Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Syntax and semantics of symbol characters in Haskell

Tags:

haskell

I'm learning Haskell.

One of my standard techniques when learning a new language is to implement a Hello World genetic algorithm that attempts to generate a string via genetic algorithm techniques that matches some input string.

Due to my inexperience with Haskell (the closest thing I have to compare is Kotlin) I searched for an example code so I could match my existing understanding of basic genetic algorithm to the code and intuit some understanding of Haskell based on my already underway reading/research on the language.

I came across this tutorial: https://www.arcadianvisions.com/blog/2011/haskell-genetic-algorithm-hello-world.html

I transcribed it in atom after setting my environment up, each part I didn't understand I did a quick google, if I didn't understand the syntax/semantics after 15 minutes I would carry on transcribing with the intent to catch up later on those particular parts.

So, I understood most of the code, such as the order of function application, monads (think I'm nearly there with monads anyway), data types, function types, currying, type substitution etc. However, there were several bits of syntax/semantics that I haven't seen in my reading/researching and not sure what they do, but they appear a lot in the code example I linked to above. I'm hoping someone can explain them to me:

(++)
(:)
<$>
<*>
(,)
(x !!)
p@(info@())

I am assuming the () and <> are some special syntax and the stuff inside them is semantic? When I hover over them in atom (I'm using atom-haskell-ghc) I can see the type Functor f => Applicative (f :: * -> *) where <*> :: f (a -> b) -> f a -> f b which kind of looks like a monad, but I don't really understand the weird syntax/semantics on the consuming end. Should I think of these as just another function (with a weird infix alias?).

Here is a specific line exhibiting several of the examples from above:

mate :: RandomGen g => Gene -> Gene -> Rand g Gene
mate gene1 gene2 = (++) <$> flip take gene1 <*> flip drop gene2 <$> pivot
  where pivot = getRandomR (0, length gene1 - 1)
like image 338
Thomas Cook Avatar asked Dec 11 '22 08:12

Thomas Cook


1 Answers

A note on operators

In Haskell you can define a function that has as identifier a sequence of symbols wrapped between brackets, like for example (++) or (:) that is an operator that can be used both as a function, like (++) x y, and as an infix operator, like x ++ y. Behind the curtains, the Haskell compiler will convert the infix operator to a function call, so x ++ y is completely equivalent with (++) x y (except for the fact that operators have different precedence rules).

(++)

This is the append function: (++) :: [a] -> [a] -> [a] it takes two lists as input, and constructs a list of the same type of elements that contains the elements of the first list followed by the elements of the second list. For example:

(++) [1, 4, 2, 5] [1, 3, 0, 2] == [1, 4, 2, 5, 1, 3, 0, 2]

(:)

This is a constructor of the list type [a]. It has type (:) :: a -> [a] -> [a]. It takes as input an element and a list of elements (all of the same type), it construct a list that starts with the first element, followed by the elements of the second parameter. For example:

(:) 1 [4, 2, 5] = [1, 4, 2, 5]

(<$>)

You wrote in your question <$>, but as you perhaps figured out, that means that somewhere a function (<$>) :: Functor f => (a -> b) -> f a -> f b is defined.

A Functor is a typeclass. Several types in Haskell are functors. The easiest ones are a list [], and a Maybe. The (<$>) takes as input a function f :: a -> b, and a functor instance, for example a list [a]. It will then convert it to a functor instance [b]. How this is done, depends on how the Functor instance is implemented (the (<$>) for a Maybe has different semantics than one for []).

Although the analogy is not complete, you can see a Functor sometimes as a collection of elements (a Maybe basically is a collection of zero Nothing, or one Just x elements). It will then map the elements the collection contains by channeling these through the function, for example:

(+1) <$> [1, 4, 2, 5] == [2, 5, 3, 6]
(+1) <$> Nothing == Nothing
(+1) <$> (Just 2) == Just 3

(<*>)

This function (<*>) :: Applicative f => f (a -> b) -> f a -> f b is again somewhat hard to understand. It makes use of an Applicative type class.

An type that is an instance of Applicative has to implement two functions: pure :: Applicative a => a -> f a and (<*>) :: Applicative f => f (a -> b) -> f a -> f b (or the programmer can decide to instead implement liftA2, but let us ignore that here).

Again you can see Applicative (at least for popular instances) as a collection (like [] or Maybe). Here we thus take as input such collection of functions (all with type a -> b), and a collection of as. We then "multiply" these, in the sense that for instance for a list:

   [f1, f2, ..., fm] <*> [x1, x2, ..., xn]
== [f1 x1, f1 x2, ..., f1 xn,
    f2 x1, f2 x2, ..., f2 xn,
    ...,
    fm x1, fm x2, ..., fm xn]

So that means for instance for Maybe that if the left operand is Nothing, or the right operand is Nothing, or both are Nothing, then this results in a Nothing, if both are Justs (so Just f <*> Just x), then we obtain a Just (f x):

Just f <*> Just x == Just (f x)
Just f <*> Nothing == Nothing
Nothing <*> Just x == Nothing
Nothing <*> Nothing == Nothing

(,)

This is the constructor of a 2-tuple: (,) :: a -> b -> (a,b) thus takes as input an a and a b, and it constructs an 2-tuple where the first item is the first parameter, and the second item is the second parameter. For example:

(,) 4 'a' == (4, 'a')

(x !!)

This is a section of an infix operator. You can use an infix operator, and for instance specify the left or the right part. In that case you thus construct a partially applied function. For example:

([1, 4, 2, 5] !!) == (!!) [1, 4, 2, 5]
(!! 2) == flip (!!) 2

So for the latter it thus means that we constructed a function that takes as input a parameter that will be filled in as left operand. So:

(!! 2) [1, 4, 2, 5] == (!!) [1, 4, 2, 5]

The (!!) :: [a] -> Int -> a function takes as input a list and an Int and it returns the element at that index (zero-based indices).

p@(info@())

In contrast to the above, the @ is not a function or operator (well these are actually the same), but a keyword.

It is used in pattern matching to obtain a reference to both an pattern and for instance match subpatterns (or obtain references to subpatterns).

For instance, say we want to pattern match a 2-tuple, and we want to have a reference to the entire tuple, and the first element, we can use:

somefunction total@(left, _) = ...

So if we then call somefunction (4, 'a'), then this means that total will hold (4, 'a'), and left will hold 4.

like image 102
Willem Van Onsem Avatar answered Dec 28 '22 12:12

Willem Van Onsem