Useful Custom Functions/ Iterators and Operators
Table of Contents
1 Useful Custom Functions/ Iterators and Operators
This is a collection of useful short code snippets.
1.1 Pipelining Operators
Haskell doesn't have a native Pipe operator like F# (F-Sharp) does, however it can be defined by the user.
> let (|>) x f = f x > > let (|>>) x f = map f x > let (?>>) x f = filter f x > take 3 (reverse (filter even [1..10])) [10,8,6] > [1..10] |> filter even |> reverse |> take 3 [10,8,6] > > [1..10] |>> (^2) |>> (/10) |>> (+100) [100.1,100.4,100.9,101.6,102.5,103.6,104.9,106.4,108.1,110.0] > > [1..10] ?>> even [2,4,6,8,10] > > [1..10] ?>> even |>> (+1) [3,5,7,9,11] > >
1.1.1 Iterators
1.1.2 Pair Iterator
Definition:
pairs alist = zip alist (tail alist)
The pairs iterator converts a list of elements to a new list of consecutive elements tuple.
Pseudo code:
pairs [e0, e1, e2, e3, e4 ...] ==> [(e0, e1), (e1, e2), (e3, e4) ...]
Let f be a function of two arguments:
f :: a -> a -> b
The function f can be applied to to the pairs sequence using the higher order function uncurry.
Pseudo code:
> g = uncurry(f) :: (a, a) -> b > g (x, y) = f x y > map uncurry(f) $ pairs [e0, e1, e2, e3, e4 ...] > [g (e0, e1), g (e1, e2), g (e2, e3), g (e3, e4) ...]
It can be useful to calculate the distance between two points, lagged difference, growth of a time series, draw a line between each two consecutive points or apply any function to two consecutive elements.
Example: Grouping Consecutive numbers
> let pairs alist = zip alist (tail alist) > pairs [1..5] [(1,2),(2,3),(3,4),(4,5)]
Example: Lagged Difference
Pseudo code:
lagdiff [e0, e1, e2, e3, e4 ...] ==> [(e1 - e0), (e2 - e1), (e3 - e2) ... ]
Development:
> let pairs alist = zip alist (tail alist) > :t pairs pairs :: [b] -> [(b, b)] > :t (-) (-) :: Num a => a -> a -> a > :t uncurry(-) uncurry(-) :: Num c => (c, c) -> c > > (-) 20 10 10 > > uncurry(-) (20, 10) 10 > > uncurry(-) (10, 20) -10 > > uncurry(flip (-)) (10, 20) 10 > > pairs [10.3, 20.5, 5.6, 8.23, 40.3] [(10.3,20.5),(20.5,5.6),(5.6,8.23),(8.23,40.3)] > > map (uncurry ( flip (-))) $ pairs [10.3, 20.5, 5.6, 8.23, 40.3] [10.2,-14.9,2.630000000000001,32.06999999999999] > > let lagdiff series = map (uncurry ( flip (-))) $ pairs series > :t lagdiff lagdiff :: Num b => [b] -> [b] > > lagdiff [10.3, 20.5, 5.6, 8.23, 40.3] [10.2,-14.9,2.630000000000001,32.06999999999999] > > lagdiff [10, 30, 5, 8, 100] [20,-25,3,92] >
Example: Distance between points on the plane.
> let pairs alist = zip alist (tail alist) {- [(X, Y)] coordinates of points in a plane -} > let points = [(1.0, 2.0), (3.0, 4.0), (-1.0, 5.0), (6.0, 6.0)] > let distance (x1, y1) (x2, y2) = sqrt( (x2-x1)^2 + (y2-y1)^2 ) > let lines = pairs points > lines [((1.0,2.0),(3.0,4.0)),((3.0,4.0),(-1.0,5.0)),((-1.0,5.0),(6.0,6.0))] > > distance (1.0, 2.0) (3.0, 4.0) 2.8284271247461903 > {- Calculate the length of each line segment -} > map (uncurry(distance)) lines [2.8284271247461903,4.123105625617661,7.0710678118654755] > > sum $ map (uncurry(distance)) lines 14.022600562229327 > > let totalLength points = sum $ map (uncurry(distance)) $ pairs (points) > > totalLength points 14.022600562229327 >
1.1.3 Triples Iterator
Definition:
triples alist = zip3 alist (tail alist) (tail $ tail alist)
Example:
> triples [1..10] [(1,2,3),(2,3,4),(3,4,5),(4,5,6),(5,6,7),(6,7,8),(7,8,9),(8,9,10)] > > :t triples triples :: [c] -> [(c, c, c)] > >
1.1.4 Sliding Window Iterator
This iterator is used in Scala and it is a generalized pairs iterator.
Definition:
sliding n alist = map (take n) (take (length(alist) -n + 1 ) $ iterate tail alist)
Example:
> sliding 3 [1..10] [[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[7,8,9],[8,9,10]] > sliding 4 [1..10] [[1,2,3,4],[2,3,4,5],[3,4,5,6],[4,5,6,7],[5,6,7,8],[6,7,8,9],[7,8,9,10]] > sliding 5 [1..10] [[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7],[4,5,6,7,8],[5,6,7,8,9],[6,7,8,9,10]] > sliding 6 [1..10] [[1,2,3,4,5,6],[2,3,4,5,6,7],[3,4,5,6,7,8],[4,5,6,7,8,9],[5,6,7,8,9,10]] > sliding 9 [1..10] [[1,2,3,4,5,6,7,8,9],[2,3,4,5,6,7,8,9,10]] >
Scala Equivalent
scala> (1 to 5).iterator.sliding(3).toList res2: List[Seq[Int]] = List(List(1, 2, 3), List(2, 3, 4), List(3, 4, 5))
1.1.5 Enumerate Iterator
Equivalent to python enumerate.
Definition:
enumerate :: [b] -> [(Int, b)] enumerate alist = zip [0..(length(alist)-1)] alist
Example:
> enumerate ['a', 'b', 'c', 'd', 'e', 'f'] [(0,'a'),(1,'b'),(2,'c'),(3,'d'),(4,'e'),(5,'f')] > take 8 (enumerate ['a'..'z']) [(0,'a'),(1,'b'),(2,'c'),(3,'d'),(4,'e'),(5,'f'),(6,'g'),(7,'h')] >
Group by length
Definition:
groupByLen n alist = filter (\a -> length(a) == n ) ( map f indexes ) where len = length(alist) indexes = map (\i -> n*i) [0..(div len n)] f idx = take n (drop idx alist)
Example:
> groupByLen 3 [1..15] [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15]] > > groupByLen 5 [1..15] [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]] > > groupByLen 6 [0..20] [[0,1,2,3,4,5],[6,7,8,9,10,11],[12,13,14,15,16,17]] > > groupByLen 3 ['a'..'z'] ["abc","def","ghi","jkl","mno","pqr","stu","vwx"] >
1.2 Applying Multiples Functions
1.2.1 Applying a list of functions to the same argument.
juxt is a function that allows apply a list of functions of same type signature to a single argument. This is useful for numerical analysis, statistics and engineering. This function was taken from the Clojure library.
juxt fs x = map ($ x) fs
Example:
> let juxt fs x = map ($ x) fs > juxt [(*3), (+4), (/10)] 30 [90.0,34.0,3.0] > > let fs = juxt [(*3), (+4), (/10)] > > :t fs fs :: Double -> [Double] > > fs 30 [90.0,34.0,3.0] > fs 40 [120.0,44.0,4.0] > > map fs [10, 20, 30] [[30.0,14.0,1.0],[60.0,24.0,2.0],[90.0,34.0,3.0]] >
1.2.2 Applying a tuple of functions to a same argument.
The family of functions juxt2, juxt3, juxt4 allow apply tuples of functions to a single argument. This is necessary when the functions don't have the same type signature.
juxt2 (f1, f2) x = (f1 x, f2 x) juxt3 (f1, f2, f3) x = (f1 x, f2 x, f3 x) juxt4 (f1, f2, f3, f4) x = (f1 x, f2 x, f3 x, f4 x) juxt5 (f1, f2, f3, f4, f5) x = (f1 x, f2 x, f3 x, f4 x, f5 x)
Example:
{- This function fails in static typed language when the functions don't have the same type signature -} > juxt [(>100), (+100)] 30 <interactive>:36:9: No instance for (Num Bool) arising from the literal `100' Possible fix: add an instance declaration for (Num Bool) In the second argument of `(>)', namely `100' In the expression: (> 100) In the first argument of `juxt', namely `[(> 100), (+ 100)]' > juxt2 ((>100), (+100)) 30 (False,130) > > > let f = juxt2 ((>100), (+100)) > :t f f :: Integer -> (Bool, Integer) > > f 30 (False,130) > > map f [10, 20, 25, 30, 100, 150] [(False,110),(False,120),(False,125),(False,130),(False,200),(True,250)] > > > juxt3 (length, maximum, minimum) [100.23, 23.23, 12.33, -123.23, -1000.23, 4000.5] (6,4000.5,-1000.23) > {- The type system fails to resolve the types in someone cases. So when it happens the developer must make the function types explicit. -} > let analytics = juxt3 (length, maximum, minimum) > > :t analytics analytics :: [()] -> (Int, (), ()) > > analytics [100.23, 23.23, 12.33, -123.23, -1000.23, 4000.5] <interactive>:79:12: No instance for (Fractional ()) arising from the literal `100.23' Possible fix: add an instance declaration for (Fractional ()) In the expression: 100.23 In the first argument of `analytics', namely `[100.23, 23.23, 12.33, - 123.23, ....]' In the expression: analytics [100.23, 23.23, 12.33, - 123.23, ....] > let analytics :: [Double] -> (Int, Double, Double) ; analytics = juxt3 (length, maximum, minimum) > > :t analytics analytics :: [Double] -> (Int, Double, Double) > > analytics [100.23, 23.23, 12.33, -123.23, -1000.23, 4000.5] (6,4000.5,-1000.23) > > import Data.Char > > let f = juxt3 (succ, pred, ord) > :t f f :: Char -> (Char, Char, Int) > > let a = map f "haskell is fun" > a [('i','g',104),('b','`',97),('t','r',115),('l','j',107),('f','d',101),('m','k',108),('m','k',108),('!','\US',32),('j','h',105),('t','r',115),('!','\US',32),('g','e',102),('v','t',117),('o','m',110)] > > unzip3 a ("ibtlfmm!jt!gvo","g`rjdkk\UShr\USetm",[104,97,115,107,101,108,108,32,105,115,32,102,117,110]) > >
1.2.3 Control Flow Functions
between
between a b x = a <= x && x <= b
> filter (between 10 20) [23, 5, 8, 17, 24, 13, 12] [17,13,12] > > filter (not . between 10 20) [23, 5, 8, 17, 24, 13, 12] [23,5,8,24]
ifelseDo
Pseudo Code:
f(x) = if pred(x) == True then: fa(x) else fb(x)
ifelseDo pred fa fb x = if pred x then fa x else fb x
Example: Apply the list the function (if x >10 then 10*x else x+5)
> let ifelseDo pred fa fb x = if pred x then fa x else fb x > map (ifelseDo (>10) (*4) (+5)) [-1, 2, 7, 8, 9, 10, 20, 30, 50] [4,7,12,13,14,15,80,120,200] > let f = ifelseDo (>10) (*4) (+5) > f 1 6 > f 3 8 > f 5 10 > > f 20 80 > f 30 120 λ> map f [-1, 2, 7, 8, 9, 10, 20, 30, 50] [4,7,12,13,14,15,80,120,200]
ifelse*
Pseudo Code:
f(x) = if pred(x) == True then: a else b
ifelse pred a b x = if pred x then a else b
Example:
If x<0 set x to -1 else set to 1
> let f = ifelse (<0) (-1) 1 > > f 2 1 > f 40 1 > f (-1) -1 > f (-10) -1 > map f [-1, -2, 3, 4, -5, -6, 0, 2] [-1,-1,1,1,-1,-1,1,1] >
ifelseEq
Pseudo Code:
f(x) = if pred(x) == True then: a else x
ifelseEq pred a x = if pred x then a else x
Example: if(x) > 10 then 30 else x
> let ifelseEq pred a x = if pred x then a else x > let f = ifelseEq (>10) 30 > map f [1, 2, 20, 10, 9, 8, 100] [1,2,30,10,9,8,30] >