DEV Community

loading...

A Haskell trip [part 2]

pmalhaire
Go / C++ / Haskell / python / Zig. Easy to read code has less bugs than elegant code.
・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"
Enter fullscreen mode Exit fullscreen mode

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

ghci
λ> :t "test"
"test" :: [Char]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Meet -> (arrow) using :type

One String is good several is more useful.

Let's concatenate 2 strings.

ghci
λ> "first" ++ "second"
"firstsecond"
Enter fullscreen mode Exit fullscreen mode

Let's get the type again.

ghci
λ> :t ++
<interactive>:1:1: error: parse error on input ++
Enter fullscreen mode Exit fullscreen mode

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]
λ>
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
λ>
Enter fullscreen mode Exit fullscreen mode

(++) 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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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’
λ>
Enter fullscreen mode Exit fullscreen mode

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’
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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

ghci
λ> (++ "-tail") "content"
"content-tail"
Enter fullscreen mode Exit fullscreen mode

Now we can do headtail like

ghci
λ> (++ "-tail") ( "head-" ++ "content" )
"head-content-tail"
Enter fullscreen mode Exit fullscreen mode

Here parenthesis are clearly needed if we omit then we got

ghci
λ> (++ "-tail") "head-" ++ "content"
"head--tailcontent"
Enter fullscreen mode Exit fullscreen mode

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

ghci
λ> ("head-" ++) ((++ "-tail") "content")
"head-content-tail"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Let's create a function.

ghci
λ> headtail a = ("head-" ++) $ (++ "-tail") a
λ> headtail "content"
"head-content-tail"
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

a is implicitly a [Char].

Let's check.

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

[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
Enter fullscreen mode Exit fullscreen mode

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 )
Enter fullscreen mode Exit fullscreen mode

Let's have a look at lisp version :

clisp
[1]> (/ (+ 3 4) (+ 4 6))
7/10
Enter fullscreen mode Exit fullscreen mode

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

Discussion (2)

Collapse
jailandrade profile image
Mikhail Cruz Andrade

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

Collapse
pmalhaire profile image
pmalhaire Author

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