Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Folding a list of functions?

Tags:

haskell

I am trying to combine an arbitrary number of mathematical functions that are in a list.

I've written a compose function that chains them together:

chain :: (c -> d) -> (a -> b -> c) -> a -> b -> d
g `chain` f = \a b -> g (f a b)

So doing something like

let b = (*) `chain` (+) `chain` (-)

Creates a function that takes a b c d, and does ((ab) + c) × d

> b 2 3 4 5
> 15

My question is, I want to be able to chain a set of these together based on an arbitrary list:

[(*),(+),(-)] would result in (*) `chain` (+) `chain` (-)

[(*),(+),(-),(*)] would result in (*) `chain` (+) `chain` (-) `chain` (*)

Is this possible? I've tried using folds but cannot get it to work since the type of the accumulator changes after each application of an element.

For example, the type of three functions chained together is:

b :: Integer -> Integer -> Integer -> Integer -> Integer

But for four it is:

b :: Integer -> Integer -> Integer -> Integer -> Integer -> Integer

If someone could point me in the right direction or knows of a solution, that would be great! Thank you.

EDIT:

My end goal is to be able to do something like this:

getZipList $ pure (function composing * and +) <*> ZipList [1,2,3] <*> ZipList [4,5,6] 
    <*> ZipList [7,8,9]

would result in:

[(1+4)*7, (2+5)*8, (3+6)*9]
like image 370
Chris Avatar asked Dec 19 '22 16:12

Chris


2 Answers

Could this idea help you develop a general function?

Prelude> :m +Data.List

Prelude Data.List> let fs = [(-),(+),(*)]

Prelude Data.List> foldl' (\accum (i,f) -> accum `f` i) 2 (zip [3..5] fs)
15
like image 23
גלעד ברקן Avatar answered Jan 14 '23 21:01

גלעד ברקן


Well, as you note yourself it can't really be possible, since different lists lengths (invisible to the type system) would result in different types. The only "solid" way do do it would be some kind of compile-time list; there are various around but none of them is really stably supported and easy to use.

The easiest alternative would be to also take the arguments as a list. i.e.,

ifxChain :: [a->a->a] -> [a] -> a

That one could easily be implemented: it's basically a zip to supply the second argument to each function, ommiting the first one. Then you fold that first parameter through the resulting list of single-parameter functions:

ifxChain fs (p0:ps) = foldl' (flip ($)) p0 $ zipWith flip fs ps

You may want to add handling of lists with non-matching lengths, should be doable reasonably safe.


If you insist on an actual variable number of function arguments (I don't think this is good!) then you need quite some type-class hackery. Such variadic functions are best known from printf.

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances     #-}

class ChainedFunc f t where
  chainIfxs :: [t->t->t] -> t -> f
  chainIfxs fs i = chainIfxPre i id $ reverse fs
  chainIfxPre :: t -> (t->t) -> [t->t->t] -> f

instance ChainedFunc t t where
  chainIfxPre i f [] = f i

instance (ChainedFunc f t) => ChainedFunc (t->f) t where
  chainIfxPre i fin (f:fs) x0 = chainIfxPre i (flip f x0 . fin) fs

Obviously, this is in many ways not nice at all. But, well, it works... ahm...

Main> chainIfxs [(*),(+),(-)] (2 :: Int) (3 :: Int) (4 :: Int) (5 :: Int) :: Int
15

like image 129
leftaroundabout Avatar answered Jan 14 '23 19:01

leftaroundabout