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 ((a − b) + 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]
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With