I don’t know yet even anything about monads, same comment about type classes.
Using VSCode adding function signatures look often veird compared to Elm.
Anyway, it’s still fun to learn and write some code in Haskell.
Here is the latest example for which I used ca. two months creating five different versions: See the source and an example of running it.
I made a new library SolarCurrent as I didn’t understand several things of the module Solar in Github, e.g. how to give the ZonedTime function arguments as described in the package Data.Time .
nice, learning is always good. the 3 instances of read $ show $ _
should probably be a call to fromIntegral
Awesome! I would actually have some use for SolarCurrent: I need to take some action when the sun rises above -7 degrees below the horizon and again take some action when the sun sinks below this threshold. (I’ve been using Python ephem for that.)
Being no astronomer, I can not make enough sense of the source code to derive how to use SolarCurrent to the above goal.
A thing you might perhaps enjoy is creating nice documentation with haddock. That would make it easier for other Haskellers to use the library.
Not in this case, as I don’t care about milliseconds of the read posix-seconds, so I round the Double to Integer and it works ok and it’s easy to calculate using the function mod the hours, minutes and seconds, as integer division remainders . However, I converted the integers back to Doubles and summed up to Double minutes to be used in further calculation.
GHCi, version 9.4.8: https://www.haskell.org/ghc/ :? for help
[1 of 3] Compiling SolarCurrent ( SolarCurrent.hs, interpreted )
[2 of 3] Compiling Main ( Hyper_Helios.hs, interpreted )
Ok, two modules loaded.
ghci> import SolarCurrent
ghci> import Data.Time
ghci> :set +t
ghci> currentTime <- getCurrentTime
currentTime :: UTCTime
ps = round $ utcTimeToPOSIXSeconds currentTime
ps :: Integral b => b
ghci> hours = read $ show $ mod (div ps 3600) 24
hours :: Read a => a
ghci> minutes = read $ show $ mod (div ps 60) 60
minutes :: Read a => a
ghci> seconds = read $ show $ mod ps 60
seconds :: Read a => a
ghci> ps
1725394416
it :: Integral b => b
ghci> (year, month, day) = toGregorian . utctDay $ currentTime
day :: DayOfMonth
month :: MonthOfYear
year :: Year
ghci> show (year, month, day)
"(2024,9,3)"
it :: String
ghci> currentTime
2024-09-03 20:13:35.986237 UTC
it :: UTCTime
For some reason hour, minutes and seconds cannot be shown in ghci REPL.
I use then here multifying by 1 as a trick to show the values.
ghci> show hours
"*** Exception: Prelude.read: no parse
ghci> 1*hours
20
it :: (Num a, Read a) => a
ghci> 1*minutes
13
it :: (Num a, Read a) => a
ghci> 1*seconds
36
As you can see, the calculated values of date and time are the same as shown in the UTC time stamp of the currentTime except the seconds with decimals.
This is interesting too, howcome it shall not cause an error, adding integer hours and double timezone without conversion integer first to double by function fromIntegral:
ghci> timeZone
2.0
it :: Double
ghci> localTime = timeZone + hours
localTime :: Double
ghci> localTime
22.0
it :: Double
Well, the timeZone is Double but hours not!
The sum result localTime is also Double.
This must be some speciality of the library Data.Time.
So the hours, minutes and seconds are not of type Integral but something odd as result of read:
hours :: Read a => a
minutes :: Read a => a
seconds :: Read a => a
I think the odd things begin already when the UTC time, currentTime is read and converted to POSIX-seconds. Why there is added the character ‘s’ to the end of seconds? And howcome it’s possible to do some trivial mathematics with it without stripping the ending s away? Are that kind of tricks common to Haskell?
ghci> currentTime <- getCurrentTime
currentTime :: UTCTime
ghci> utcTimeToPOSIXSeconds currentTime
1725401595.989455s
it :: time-1.12.2:Data.Time.Clock.Internal.POSIXTime.POSIXTime
Python’s name ephem must be from ephemeris, meaning:
A table giving the coordinates of a celestial body at a number of specific times during a given period. Well, I’ll look at it.
I agree, a good documentation is important, so I’ll check haddock.
My module SolarCurrent contains exactly the same collection of astronomic functions as the spreadsheets by NOAA (in Excel, Open Office / Libre Office)
I have very little commenting there in the source because all is completely and very well explained on the NOAA pages.
Btw, the limit -7 degrees below horizon is near the common civil twilight limit -6 degr below horizon, used often as a clock time result in the solar calculator programs.
This read
/show
business really is something to be avoided. You’re turning a number into a string and then back into a number for no apparent reason. This is also the reason you’re unable to print the result in the REPL without doing something extra to it.
What problems do you encounter with
ghci> hours = mod (div ps 3600) 24
hours :: Integral a => a
ghci> hours
20
it :: Integral a => a
(or, as it is more commonly written, hours = ps `div` 3600 `mod` 24
)?
Indeed that’s what it is, possibly corrected for atmospheric aberration? Civil twilight is what our local regulatory authorities deem the activity period of predatory birds, which are to be protected.
Then it is good custom to have the haddock documentation link to the external documentation.
Another suggestion for improvement: Consider newtype
aliases for latitude and longitude, possibly indicating the implicit reference system, as in
newtype Latitude = LatWGS84 {getLatitude :: Double}
deriving (necessary type classes here)
That way, your code can not accidentally mix up latitude and longitude or other Double
quantities. As a side-effect, the type signatures of functions like solarElevationAngle
become much more readable. For things like elevation angles, with a newtype you could also build in modulo operations, so that adding angles automatically wraps around a full circle.
SolarCurrent.dateString
: That is what Data.Time.Format.formatTime
is for.
There are a few numeric type classes that allow conversions in Haskell:
-- embeds the integer in any 'Num' type.
-- used by the compiler for integer literals in source code
fromInteger :: Num a => Integer -> a
-- careful: overflows without warning!
-- >>> fromIntegral (1000 :: Int) :: Word8
-- 232
fromIntegral :: (Integral a, Num b) => a -> b
toRational :: Real a => a -> Rational
-- embeds fractions in any 'Fractional' type.
-- used by the compiler for literals like 3.141
fromRational :: Fractional a => Rational -> a
properFraction :: (RealFrac a, Integral b) => a -> (b, a)
All time difference types in the time
library are members of RealFrac
, Real
, Fractional
and Num
so all the above functions can be used on them.
No problems within ghci.
The following happend as I replaced the lines using
read $ show $ with the new ones without conversions:
main = do
currentTime <- getCurrentTime
let (year, month, day) = toGregorian . utctDay $ currentTime
ps = round $ utcTimeToPOSIXSeconds currentTime
-- hours = read $ show $ mod (div ps 3600) 24
-- minutes = read $ show $ mod (div ps 60) 60
-- seconds = read $ show $ mod ps 60
hours = mod (div ps 3600) 24
minutes = mod (div ps 60) 60
seconds = mod ps 60
jC = julianCentury currentTime
sunDecl = sunDeclin jC
% ghci SolarCurrent.hs
GHCi, version 9.4.8: https://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling SolarCurrent ( SolarCurrent.hs, interpreted )
Ok, one module loaded.
ghci> main
<interactive>:2:1: error:
Variable not in scope: main
Suggested fix: Perhaps use ‘min’ (imported from Prelude)
ghci>
• No instance for (Integral Double) arising from a use of ‘round’
• In the first argument of ‘($)’, namely ‘round’
In the expression: round $ utcTimeToPOSIXSeconds currentTime
In an equation for ‘ps’:
ps = round $ utcTimeToPOSIXSeconds currentTimetypecheck(-Wdeferred-type-errors)
That was originally the reason why I use conversion Double → Integer with show and read. However, I believe there is a way how to avoid conversions completely.
First the most simple solution: leaving out the local time
In that case I could remove the variables hours, minutes, seconds
and get the minutes simply as Double through currentTime / 60.0 to be used next by several NOAA astronomical functions.
Maybe there is a function in Data.Time getting the local time with the given timezone from the current UTC time.
Also, the error looks like it’s because ps
is a Double, but round expects to return an Int or other integral type. You can do ps = fromInteger $ round $ ...
to tell the compiler to round to Integer, then convert to Double
I have just uploaded the latest improved version which has no read $ show $ sequences anymore.
The old version mod instructions are replaced with a function found from stackoverflow.com
It can be used for remainders of non-integer division.
nonIntRem :: RealFrac a => a -> a -> a
nonIntRem x y = x - (y * fromIntegral (truncate (x/y)))
Nice! FYI ansi-terminal if you dont want to manage color codes manually
I would rather use CSS so as I do in Elm.
I have not studied yet, how to do that in Haskell.
Well, it’s nostalgic, long time ago I used ANSI-codes previously writing RSTS-11 Basic code for DEC PDP and VAX terminals in job.
Yes, I have now tried haddock and I enjoy it!
It looks nice really.