Py2hs
A complementary resource that helps python programmers to learn Haskell
Install / Use
/learn @cffls/Py2hsREADME
py2hs
Introduction
I find it an interesting practice to learn and reinforce a new programming language by comparing it with a familiar language, and adding the distinct features and concepts from the new language on top of my existing knowledge system. This is a reference guide I created when I was learning Haskell.
This project is inspired by py2rs.
This is:
- a complementary resource that helps python programmers to learn Haskell as a new language
- a place for beginners to learn and reinforce some of the core concepts in Haskell
This is not:
- an one-stop-shop that teaches everything about Haskell
- an encyclopedia that aims at covering all similarities and differences between Python and Haskell
Table of contents
- General
- Functions
- List
- Class -> data
- Interface -> Type class
- Higher order functions
- Functor
- Applicative
- Monoid
- Monad
General
| Definition | Python | Haskell | | -------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------- | | Programming paradigm | Imperative | Purely functional | | Type System | Dynamically typed | Statically typed | | First appeared | 1989 | 1990 | | File extensions | .py, .pyw, .pyc | .hs, .lhs | | Programming guidelines | PEP8 | Haskell programming guidelines |
Getting started with Haskell
Official Haskell documentation provides plenty of tutorials and books to start with.
https://www.haskell.org/documentation/
Code format
All examples are either code snippet or interactions with python shell or Haskell GHCi.
Python shell examples starts with >>> , and Haskell GHCi examples starts with λ> . Please note that all
Python codes are written in Python 3.
Functions
Basic function syntax
Python
>>> def multiply(x, y):
... return x * y
...
>>> multiply(2, 3)
6
Haskell
λ> multiply x y = x * y
λ> multiply 2 3
6
Flow control
Python
def bigger(x, y):
if x > y:
return x
else:
return y
Haskell
bigger x y = if x > y
then x
else y
Explicit typing
Python
def bigger(x: int, y: int) -> int:
if x > y:
return x
else:
return y
Haskell
Approach 1: explicitly specify all inputs and the output
bigger :: Int -> Int -> Int
bigger x y = if x > y
then x
else y
Approach 2: set a type constraint Integral for a
bigger :: (Integral a) => a -> a -> a
bigger x y = if x > y
then x
else y
Lambda function
Python
>>> list(map(lambda x: x + 1, [1, 2, 3]))
[2, 3, 4]
Haskell
λ> map (\x -> x + 1) [1, 2, 3]
[2,3,4]
Comments
Python
# This is a python comment
'''
Python
multiline
comment
'''
Haskell
-- This is a haskell comment
{-
haskell
multiline
comment
-}
List
List creation
Python
>>> [1, 2, 3, 4]
[1, 2, 3, 4]
>>> list(range(1, 5)) # Use range
[1, 2, 3, 4]
Haskell
Syntax sugar:
λ> [1, 2, 3, 4]
[1,2,3,4]
λ> [1..4] -- Use range
[1,2,3,4]
Without syntax sugar:
λ> 1:2:3:4:[]
[1,2,3,4]
List concatenation
Python
>>> [1, 2] + [3, 4]
[1, 2, 3, 4]
Haskell
λ> [1, 2] ++ [3, 4]
[1,2,3,4]
Adding element to list
Append
Python
>>> my_list = [1, 2, 3, 4]
>>> my_list.append(5)
>>> my_list
[1, 2, 3, 4, 5]
Haskell
λ> [1, 2, 3, 4] ++ [5]
[1,2,3,4,5]
Prepend
Python
>>> my_list = [1, 2, 3, 4]
>>> my_list.insert(0, 5)
>>> my_list
[5, 1, 2, 3, 4]
Haskell
λ> 5:[1, 2, 3, 4]
[5,1,2,3,4]
Indexing
Python
>>> my_list = [1, 2, 3, 4]
>>> my_list[2]
3
Haskell
λ> let myList = [1, 2, 3, 4]
λ> myList !! 2
3
List comprehension
Python
>>> [i * 2 for i in range(1, 11)]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Haskell
λ> [x*2 | x <- [1..10]]
[2,4,6,8,10,12,14,16,18,20]
List comprehension with conditions
Python
>>> [i * 2 for i in range(1, 11) if i % 2 == 0]
[4, 8, 12, 16, 20]
Haskell
λ> [x*2 | x <- [1..10], even x]
[4,8,12,16,20]
More complex list comprehension
Python
>>> [i * j for i in range(1, 5) for j in range(1, 5)]
[1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, 4, 8, 12, 16]
>>> [[a, b, c] for c in range(1, 11)
... for b in range(1, c+1)
... for a in range(1, b+1) if a**2 + b**2 == c**2]
[[3, 4, 5], [6, 8, 10]]
Haskell
λ> [ x*y | x <- [1..4], y <- [1..4]]
[1,2,3,4,2,4,6,8,3,6,9,12,4,8,12,16]
λ> [ [a,b,c] | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2]
[[3,4,5],[6,8,10]]
Zipping
Python
>>> list(zip(range(1,5), range(2,6)))
[(1, 2), (2, 3), (3, 4), (4, 5)]
>>> list(zip(range(1, 5), [8, 9, 10]))
[(1, 8), (2, 9), (3, 10)]
Haskell
λ> zip [1..4] [2..5]
[(1,2),(2,3),(3,4),(4,5)]
λ> zip [1..4] [8,9,10]
[(1,8),(2,9),(3,10)]
Class -> data
In Python, and most OOP languages, Classes provide a means that bundles two things:
- data
- functions that retrieve, modify, or manipulate the data
Let's start with defining a class in Python.
from dataclasses import dataclass
@dataclass
class Square:
side: float
def area(self):
return self.side ** 2
>>> my_square = Square(5)
>>> my_square.area()
25
>>> my_square.side = 6
>>> my_square.area()
36
Class Square bundles a float number, side, and a function, area, which calculates the area of the square.
Attribute side could be directly updated. The functions bundled with the class are stateful, because
the outputs of the same function could be different depending on the state of the class. Being stateful is like holding
a double-edge sword. On one hand, you can make a class very flexible and adaptive to new changes. On the other
hand, however, the behavior of functions could be non-deterministic with respect to their inputs, making them
unpredictable, and therefore, difficult to debug.
Now let's see a way to define a Square and a function that calculates any Square's area in Haskell.
data is a keyword in Haskell. It defines a type of data structure, similar to Class in Python.
data Square = Square Float -- Defining "Square" as a data structure that holds a Float
area :: Square -> Float -- Declaring function "area" as a function that takes a "Square" and returns a Float
area (Square side) = side ^ 2 -- Implement the area function
λ> let mySquare = Square 5
λ> area mySquare
25.0
What we saw above, is a type (Square) that holds a float, and a function (area) that operates on this specific
type, while not being bundled to the data structure itself. The separation of data and function, is one of the
most important characteristics of Haskell.
What's good about the separation of data and function? Stateless! Instead of depending on some "internal"
state, functions are deterministic and predictable purely from their inputs.
Interface -> Type class
Area does not only apply to square, but all types of shapes. Let's start with defining an interface, area, in Python.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
Then we define a couple of shapes that implements area.
import math
from dataclasses import dataclass
@dataclass
class Square(Shape):
side: float
def area(self):
return self.side ** 2
@dataclass
class Rectangle(Shape):
