loading...

A Haskell trip [part 2]

pmalhaire profile image pmalhaire ・7 min read

A Haskell trip [part 2] : let's discover haskell syntax

Introduction

Now that the high level design is clear enough to be used. Let's introduce haskell. This project is my first in this wonderful language and the opportunity to introduce the language with my fresh eyes.

Let's enter the Haskell world

Let's take a breath and introduce Haskell. Sometime when I go to conferences I fell like Haskell is a label for programmers. If you know Haskell you must be smart. Well ... maybe that's why you are reading my post. Smart or not Haskell is nerd fun.

When you learn new languages it's practical to use analogies from one language to an other. A lot among us learned Html then Javascript then PHP then Cpp or other order/combination including python, lua, golang, ruby or many other languages.
Learning Haskell is not a jump from the previous language you learned (unless you've studied an other functional language). It's a new world. Each new Haskell line will learn you something : a brand new vision of computer programming !

Introducing Haskell is not that easy since it's a jump to a new world. I'll take a rather unusual way, if you want "classical" ways you'll see interesting books in the references. The goal here is to introduce concepts we needed to create askBarrel.

Meet the haskell syntax

Haskell is a rich universe with many entrance doors.

Mine is somehow singular, I find Haskell syntax graphically beautiful.

One thing that can loose you when learning haskell is the symbols such as =>, ::, ->, $,<$. It's based on many useful concepts. Once the concept is yours the symbol will come naturally. You'll ask yourself I want to do that I know there is a glyph for it.

The goal of this project is to make a REPL parser. Our main objects are strings (a list of char [Char]) so we'll focus on it.

A useful link to help you google about symbols.

Meet :: using :type

Let's write a string test in ghci.

ghci
λ> "test"
"test"

Let's use :type (or it short form :t) in ghci after all Haskell is all about type.

ghci
λ> :t "test"
"test" :: [Char]

We meet here the :: symbol. Double colon or type signature.
It tells the compiler that our string is of type [Char] a list of Char.

[Char] is the default type used for string but we can use :: to force an other type String for example.

ghci
λ> "test"::String
"test"
λ> :t "test"::String
"test"::String :: String

Meet -> (arrow) using :type

One String is good several is more useful.

Let's concatenate 2 strings.

ghci
λ> "first" ++ "second"
"firstsecond"

Let's get the type again.

ghci
λ> :t ++
<interactive>:1:1: error: parse error on input ++

Oops, it seems that ++ no type. In fact is does have one but ++ is composed of special character + to get it's type we need to use parenthesis.

ghci
λ> :t (++)
(++) :: [a] -> [a] -> [a]
λ>

This strange but beautiful [a] -> [a] -> [a] explains that ++ is a function of 2 variables of type [a] returning the type [a]. You may wonder why we have several -> it's because any function in haskell can be seen as a construction made of functions of one variable, it may seem strange at first but it's one of the things that make haskell powerful.

Let's break ++ into parts adding parenthesis.

ghci
λ> ("first" ++) "second"
"firstsecond"
λ> :t ("first" ++)
("first" ++) :: [Char] -> [Char]

So ("first" ++) type signature (right of ::) is [Char] -> [Char].

From an argument of type [Char] return (->) one value of type [Char].

Let's go back on (++).

ghci
λ> :t (++)
(++) :: [a] -> [a] -> [a]
λ>

(++) type signature [a] -> [a] -> [a] tells us that :

  • if I give one argument to ++ such as in ("first" ++) it will return a function taking [a] and returning [a].
  • if I give 2 arguments to ++ such as in "first" ++ "second" it will return a instance of type [a].

This kind of manipulation will be very useful as it allows to chain multiple function doing the calculation only when the result is needed. It's called Currying and gave it's name to Haskell since Haskell Curry invented this.

It works as expected. Emina my noble colleague made me realize that it was not that instinctive. Meanwhile we are looking at one of Haskell's beauty. A way to see it is to cut type signature as follow.

-- to use the function entirely give it 2 parameters
-- | parameters     | result |
--   [a] ->   [a]  ->    [a]
λ> "first-" ++ "-second"
"first--second"
-- to use partially the function give it only one parameters
-- | parameter   |      result |
--    [a]       ->  [a] -> [a]
-- here we will have a function you'll need one more `[a]` to get a result such as ("first-" ++)
λ> f=("first-" ++)
-- ("first-" ++) it used as a brick once
λ> f "test"
"first-test"
-- ("first-" ++) it used as a brick twice
λ> f ( f "test")
"first-first-test"

https://dev.to/pmalhaire/an-haskell-trip-part-2-kii

Meet the => double arrow using :info

An other very useful function in ghci is info it gives all information about a type, a variable or a function.

λ> :t 1
1 :: Num p => p

Here our type signature (::) as an extra =>.
This add a constraint to the type p. p must be any type that is Num instance.

Let's get more about it using :info.

ghci
λ> :info Num
class Num a where
  (+) :: a -> a -> a
  (-) :: a -> a -> a
  (*) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
  {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
    -- Defined in ‘GHC.Num’
instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’
λ>

To be valid the type p must implement the 7 functions defined in the class Num.

Note : the class here is not the one of object oriented programing.

In our ghci context the available instances of Num are

instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

We could implement https://dev.to/pmalhaire/an-haskell-trip-part-2-kiimore if we wanted to. It's an other beauty of haskell we tell a contract for variable a here Num and any instance matching the contract can be used Word, Integer, etc.

ghci
λ> :t 1.1
1.1 :: Fractional p => p
λ> :t 1
1 :: Num p => p
λ> 1.1 +1
2.1

The case of $

Consider the following we want to add a head and a tail to our string.

ghci
λ> "head-" ++ "content" ++ "-tail"
"head-content-tail"

It will be more usefull to do something like headtail "content".

Let's reuse what we have done before.

"head-" is to be added first so we are fine

ghci
λ> "head-" ++ "content"

"-tail" in must be added after.
We will create a function that adds "-tail" after.

ghci
λ> (++ "-tail") "content"
"content-tail"

Now we can do headtail like

ghci
λ> (++ "-tail") ( "head-" ++ "content" )
"head-content-tail"

Here parenthesis are clearly needed if we omit then we got

ghci
λ> (++ "-tail") "head-" ++ "content"
"head--tailcontent"

We can even do better using a partial function for head as well.

ghci
λ> ("head-" ++) ((++ "-tail") "content")
"head-content-tail"

It's good but it's a lot of parenthesis.

Let me introduce $. It's an operator to use less parenthesis, it wraps what is after it with parenthesis.

ghci
λ> ("head-" ++) $ (++ "-tail") "content"
"head-content-tail"

Let's create a function.

ghci
λ> headtail a = ("head-" ++) $ (++ "-tail") a
λ> headtail "content"
"head-content-tail"

Usually function argument are written a (then b) for variables and f (then g) for functions.
Here a is the argument of headtail.

Here we did not put any constraint on the type of a. Since it's used by (++ "-tail") it must conform to it's type.

ghci
λ> :t (++ "-tail")
(++ "-tail") :: [Char] -> [Char]

a is implicitly a [Char].

Let's check.

ghci
λ> headtail a = ("head-" ++) $ (++ "-tail") a
λ> :t headtail
headtail :: [Char] -> [Char]

[Interlude] The smell of lisp

Haskell reminds me the time I studied lisp in the IA course of professor Tupia in the Pontificia Universidad Catolica del Péru.

Lets have a look a it in ghci see this link to install haskell.

ghci
λ> ( 3 + 4 ) / ( 4 + 6 )
0.7

This simple calculation is using infix operators. It's an instinctive thing for most of us.

Those are all equivalent :

( 3 + 4 ) / ( 4 + 6 )
(/) ( 3 + 4 ) ( 4 + 6 )
(/) ( (+) 3 4 ) ( (+) 4 6 )

Let's have a look at lisp version :

clisp
[1]> (/ (+ 3 4) (+ 4 6))
7/10

It's one of the many reasons why lispand haskell community are connected.

I did not focus on the fact that ++ is an infix function. To get to understand it try to move ++ one side and the other (++ "string") and ("string" ++).

For more details see infix functions.

Conclusion

https://dev.to/pmalhaire/an-haskell-trip-part-2-kii
We met Haskell syntax with the goal manipulate strings for a REPL client. In part 3(not published yet) we'll tackle the M word the mythical creature called Monad.

Appendix : references

Posted on by:

pmalhaire profile

pmalhaire

@pmalhaire

Go / C++ / Haskell /python. Easy to read code has less bugs than elegant code.

Discussion

pic
Editor guide
 

as someone who is starting to program in Haskell I thank you for this post

 

Thanks a lot.
The next post will be finished in a week.