Today.hs
This is a very simple tool which I wrote just to test some basic haskell stuff. I think this could help some others.
Just do make
and then run ./today
notice that you will need hspec to make it.
cabal install hspec
Have fun!
Content from my blogpost: Hello folks,
I want to write a small tutorial on how to write a simple CLI-tool in Haskell.
For all who did not know, Haskell is a pure functional programming language which was released in 1990. What I personally like the most is list comprehension, this is a really powerful and great feature. But there are also other really cool things like maybe for example. It is really packed with a lot of cool stuff. iHigher order functions, currying or partial application are very useful features too. We will see that Haskell functions are very small, but very powerful. I've read the sentence that you describe what a value is and not how you get to this value. What makes it really pretty. If you want to take a further look I really can recommend this guide through the world of Haskell. If you want some real projects, Facebook anti-spam algorithms are implemented in Haskell for example.
The tool we will write now will not do much, it will simply give us the current day to our terminal.
To start you should have make(which you sure already have) ghc - the glasgow haskell compiler and cabal (for a testing-library) installed.
Finally, just open a terminal.
Create a directory called today.hs. mkdir today.hs && cd today.hs
Create a Main.hs file and a Makefile - touch Main.hs Makefile
.
Lets start with our Main.hs file
-- our main function
main :: IO ()
main = do
putStrLn "Hello Haskell!"
Main.hs
To test it write ghc -Wall Main.hs
in our terminal.
You should see something like this as output:
$ ghc -Wall Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
Alright, after an ls -al you could see that there are new files called
Main.o
, Main.hi
and Main
.
Main
is the target file, the other ones are temporarily created files from the ghc
within the compile process. These can safely be deleted.
Now execute ./Main
and you should see Hello Haskell!
on your terminal.
Because we don't always want to remember which flags and options we used let's write a makefile for that!
# the compile with -Wall flags(all errors and warnings)
COMPILER = ghc -Wall
# our main module
MAIN = Main
# will execute other targets
all: target clean
# depents on $(MAIN).hs - will be executed if Main.hs is changed
target: $(MAIN).hs
# ghc -Wall Main.hs
$(COMPILER) $(MAIN).hs
# cleanup
clean:
# remove temporary compile files
rm $(MAIN).o $(MAIN).hi
Makefile
NOTICE: you have to use tabs in the makefile or it won't work.
Now type make
into your terminal.
$ make
ghc -Wall Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
rm Main.o Main.hi
Pretty cool, huh. You could see all commands and the output of the commands which are used and we automatically get rid of the unused files.
Fine, now we are forming the "Hello Haskell!" into a function. How do we do that? Simply so:
-- our main function
main :: IO ()
main = do
putStrLn helloHaskell
-- helloHaskell returning a string
helloHaskell :: String
helloHaskell = "Hello Haskell!"
Main.hs
Pretty simple right?
The function name followed by ::
defines it's type signature. Every function has
a type signature. Most of the time Haskell can think of itself which type
signature would be the right. But you should always write a type signature
yourself on your functions because you could write what you are intended to do and
find errors early. If you want to see some type signatures of predefined functions
you could start ghci via ghci
, which comes with ghc in your terminal. Then simply type :t function
for example the output of :t head
is head :: [a] -> a
This means it
takes a list([]) of a's(every type) and returns a single a(of the same type).
Lists can only contain of the same data type in Haskell, you couldn't mix chars,
integers and so on. Some functions also have type classes, for example <.
:t (<)
is (<) :: Ord a => a -> a -> Bool
This means a has to be of the typeclass
Ord, and the functions takes an a and an a - so two a's - and returns a bool.
2 < 3 = True. The type after the last -> is always the return value, because every
function can only return one value. If it returns functions or takes functions as
an argument you would wrap that in parentheses like map. :t map
map :: (a -> b) -> [a] -> [b]
map takes a function from type a to b(they could also be the same, but don't have
to), a list of a's and returns a list of b's.
If we google for 'timestamp Haskell' we could find this module: Data.Time.Clock.POSIX
If we try that in the ghci with :m Data.Time.Clock.POSIX
and type getPOSIXTime
we see that this is actually with some positions after the decimal point. To cut
that we can use fmap
like : fmap round getPOSIXTime
import Data.Time.Clock.POSIX(getPOSIXTime)
-- our main function
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn (show timestamp)
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = round `fmap` getPOSIXTime
Main.hs
If we now compile and start our program we will see the current timestamp \o/
Alright and what next? From the timestamp we now can calculate how many days are elapsed since the timestamp started running. How do we do that? A day has 86400 seconds, so we simply divide the timestamp by this number and we get the result.
import Data.Time.Clock.POSIX(getPOSIXTime)
-- our main function
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn (show $ calculateElapsedDays timestamp)
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = round `fmap` getPOSIXTime
-- calulate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
Main.hs
The $ is only to avoid parentheses. We could also write putStrLn(show (calculateElapsedDays timestamp))
But this get a bit messy if it grows.
If you are interested you can as always enter ghci and type :t ($)
it will give you ($) :: (a -> b) -> a -> b
$ takes af function from a to b and an a and returns a b. Look here.
Notice that we have used the infix operator of div, which is used with backticks,
we could also write div ts 86400
it is the same, but for readability I prefer
the infix variant on this functions.
Because we are lazy, lets tweak our Makefile a bit. We add a target start which will start the compiled program, so that we don't have to do this manually each time we change something
# the compile with -Wall flags(all errors and warnings)
COMPILER = ghc -Wall
# our main module
MAIN = Main
# will execute other targets
all: target clean
# depents on $(MAIN).hs - will be executed if Main.hs is changed
target: $(MAIN).hs
# ghc -Wall Main.hs
$(COMPILER) $(MAIN).hs
# cleanup
clean:
# remove temporary compile files
rm $(MAIN).o $(MAIN).hi
# start
start:
./$(MAIN)
Makefile
Run make and you will see how many days are elapsed since the glorious creation of the timestamp.
$ make
ghc -Wall Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
rm Main.o Main.hi
./Main
16977
Now we need a function which calculates us the current index of the day based on the elapsed days.
import Data.Time.Clock.POSIX(getPOSIXTime)
-- our main function
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn (show $ calculateDayIndex . calculateElapsedDays $ timestamp)
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = round `fmap` getPOSIXTime
-- calulate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
calculateDayIndex :: Integer -> Integer
calculateDayIndex x = x `mod` 7
Main.hs
Simple as that. The .
is just a function composition like in maths.
(.) :: (b -> c) -> (a -> b) -> a -> c
. It takes a function from b to
c(calculateDayIndex),
a function from a to b(calculateElapsedDays), and a(timestamp) and calculates a c -
the result. The mod function is just the modulo function.
Now run make and I get this now:
$ make
ghc -Wall Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
rm Main.o Main.hi
./Main
2
This will be Wednesday if we start at 0 but now is Saturday. If we do a bit research we get that the 01.01.1970 was a Thursday, so we have to place an offset. Adjust the function like this:
import Data.Time.Clock.POSIX(getPOSIXTime)
-- our main function
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn (show $ calculateDayIndex . calculateElapsedDays $ timestamp)
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = round `fmap` getPOSIXTime
-- calulate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
-- add 3 beacuse the 01.01.1970 was a Thursday
calculateDayIndex :: Integer -> Integer
calculateDayIndex x = (x + 3) `mod` 7
Main.hs
And Again:
$ make
ghc -Wall Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
rm Main.o Main.hi
./Main
5
Now we are at the point where we need to define a type which is representing our days.
We will do this like this:
import Data.Time.Clock.POSIX(getPOSIXTime)
-- Our Day data type
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Show, Enum)
-- our main function
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn (show $ calculateDayIndex . calculateElapsedDays $ timestamp)
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = round `fmap` getPOSIXTime
-- calulate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
-- add 3 beacuse the 01.01.1970 was a Thursday
calculateDayIndex :: Integer -> Integer
calculateDayIndex x = (x + 3) `mod` 7
Main.hs
We derive from the Show data type to make the days printable and the Enum data type.
Now we can write a function that gives us the current day from the index. Let's do this.
import Data.Time.Clock.POSIX(getPOSIXTime)
-- Our Day data type
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Show, Enum)
-- our main function
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn (show $ getDayFromIndex . calculateDayIndex . calculateElapsedDays $ timestamp)
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = round `fmap` getPOSIXTime
-- calulate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
-- add 3 beacuse the 01.01.1970 was a Thursday
calculateDayIndex :: Integer -> Integer
calculateDayIndex x = (x + 3) `mod` 7
-- returns a day of a given index
getDayFromIndex :: Int -> Day
getDayFromIndex x = toEnum $ fromIntegral x :: Day
Main.hs
And that's it, this will work but we can do a bit better.
Now we add an so called edge case to our getDayFromIndex function if it were called with a to high or low argument, because we soon split it into modules and we can't be sure that anybody else will use this module somewhere else.
import Data.Time.Clock.POSIX(getPOSIXTime)
-- Our Day data type
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Show, Enum)
-- our main function
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn (show $ getDayFromIndex . calculateDayIndex . calculateElapsedDays $ timestamp)
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = round `fmap` getPOSIXTime
-- calulate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
-- add 3 beacuse the 01.01.1970 was a Thursday
calculateDayIndex :: Integer -> Integer
calculateDayIndex x = (x + 3) `mod` 7
-- returns a day of a given index
getDayFromIndex :: Int -> Day
getDayFromIndex x
| x <= 0 || x > 6 = error "Index must be between 0 and 6"
| otherwise = toEnum $ fromIntegral x :: Day
Main.hs
We use so called guards(|) here. Guards are matching the arguments to a given pattern and return something if this case is true. So if x is lower than zero or bigger than 6 we throw an error.
But because we are cool developer we want to split this into modules.
Create a new file called Today.hs
and adjust it like this:
module Today (Day(..), getCurrentTimestamp, getDayFromTimestamp) where
import Data.Time.Clock.POSIX(getPOSIXTime)
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Show, Enum)
-- Returns the current day
getDayFromTimestamp :: Integer -> Day
getDayFromTimestamp ts = getDayFromIndex .
fromIntegral .
calculateDayIndex .
calculateElapsedDays $
ts
-- returns a day of a given index
getDayFromIndex :: Int -> Day
getDayFromIndex x
| x < 0 || x > 6 = error "Index must be between 0 and 6"
| otherwise = toEnum x :: Day
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = (round `fmap` getPOSIXTime)
-- calculate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
-- add 3 beacuse the 01.01.1970 was a Thursday
calculateDayIndex :: Integer -> Integer
calculateDayIndex x = (x + 3) `mod` 7
Today.hs
The things in parentheses after the module are the things we are exporting to others. With the Day(..) we will export the complete Day data type.
Now we adjust our Main.hs
file:
import Today (getCurrentTimestamp, getDayFromTimestamp)
main :: IO ()
main = do
timestamp <- getCurrentTimestamp
putStrLn
(show $ getDayFromTimestamp timestamp)
Main.hs
Of course we need to adjust our Makefile too:
COMPILER = ghc -Wall
PROGNAME = Today
LOWER_PROGNAME = $(shell echo $(PROGNAME) | tr A-Z a-z)
MAIN = Main
all: target clean start
target: $(PROGNAME).hs $(MAIN).hs
$(COMPILER) -o $(LOWER_PROGNAME) $(MAIN).hs
clean:
rm $(PROGNAME).hi $(PROGNAME).o
rm $(MAIN).hi $(MAIN).o
start:
./$(LOWER_PROGNAME)
Makefile
Just run make and it works:
$ make
ghc -Wall -o today Main.hs
[1 of 2] Compiling Today ( Today.hs, Today.o )
[2 of 2] Compiling Main ( Main.hs, Main.o )
Linking today ...
rm Today.hi Today.o
rm Main.hi Main.o
./today
Saturday
You notice that we now have a binary called today and not Main as before. So now, of course we should write tests to test our api!
I've mentioned that you need cabal installed and now we will use it.
We will use the hspec package.
So just type cabal install hspec
and that is all.
Create a file Test.hs
import Test.Hspec
import Test.QuickCheck
import Control.Exception (evaluate)
import Today
main :: IO ()
main = hspec $ do
describe "Today" $ do
it "get the right weekday from a timestamp" $ do
getDayFromTimestamp 0 `shouldBe` Thursday
getDayFromTimestamp 1466766758 `shouldBe` Friday
getDayFromTimestamp 1466881750`shouldBe` Saturday
Test.hs
If we now run runhaskell Test.hs
we could see an error message.
No instance for (Eq Day) arising from a use of ‘shouldBe’
This tells us that
we have to add the Eq type class to our Day datatype, because shouldBe only works
on this kind of Types.
So we adjust our Day dataype like this:
module Today (Day(..), getCurrentTimestamp, getDayFromTimestamp) where
import Data.Time.Clock.POSIX(getPOSIXTime)
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Show, Enum)
-- Returns the current day
getDayFromTimestamp :: Integer -> Day
getDayFromTimestamp ts = getDayFromIndex .
fromIntegral .
calculateDayIndex .
calculateElapsedDays $
ts
-- returns a day of a given index
getDayFromIndex :: Int -> Day
getDayFromIndex x
| x < 0 || x > 6 = error "Index must be between 0 and 6"
| otherwise = toEnum x :: Day
-- returns the current timestamp
getCurrentTimestamp :: IO Integer
getCurrentTimestamp = (round `fmap` getPOSIXTime)
-- calculate the elapsed days since 01.01.1970
calculateElapsedDays :: Integer -> Integer
calculateElapsedDays ts = ts `div` 86400
-- add 3 beacuse the 01.01.1970 was a Thursday
calculateDayIndex :: Integer -> Integer
calculateDayIndex x = (x + 3) `mod` 7
Today.hs
Run runhaskell Test.hs
again and you could see our test pass.
We want that our tests are running everytime before we compile this tool. So back to our Makefile.
COMPILER = ghc -Wall
PROGNAME = Today
LOWER_PROGNAME = $(shell echo $(PROGNAME) | tr A-Z a-z)
TEST = Test.hs
MAIN = Main
all: test target clean start
test: $(PROGNAME).hs $(MAIN).hs $(Test)
runhaskell $(TEST)
target: $(PROGNAME).hs $(MAIN).hs
$(COMPILER) -o $(LOWER_PROGNAME) $(MAIN).hs
clean:
rm $(PROGNAME).hi $(PROGNAME).o
rm $(MAIN).hi $(MAIN).o
start:
./$(LOWER_PROGNAME)
Makefile
If we now run make
we could see how our tests are running first.
$ make
runhaskell Test.hs
Today
get the right weekday from a timestamp
Finished in 0.0003 seconds
1 example, 0 failures
ghc -Wall -o today Main.hs
[1 of 2] Compiling Today ( Today.hs, Today.o )
[2 of 2] Compiling Main ( Main.hs, Main.o )
Linking today ...
rm Today.hi Today.o
rm Main.hi Main.o
./today
Saturday
And that is all it takes to make a small simple CLI-tool. I made this for me just for learning purpose but I thought others would be interested in this too.
If you like this or have any constructive critic share this or drop me a line.
You could see the whole project on github as reference.
Thanks!