简体   繁体   中英

Haskell - List of functions that operate on single value

I'm new to Haskell and am doing some beginner exercises I found. I have a function that takes in a list of functions and a value. I need send that value through each function in the list, beginning at the head, and return that value. I've considered recursion and folds...but I think some kind of recursive approach would be the way to go.

I attempted a recursive approach like below, but it gets to the base case, returns, and I can't combine it properly

func xs y =
 if length xs == 1 then
  (head xs) y
 else
  (head xs) y : func (drop 1 xs) y

I just can't figure it out! Any help would be great, thank you!!!

Misunderstood version

So, first of all, I would recommend to add a type signature, that helps you because the compiler will give you better error messages:

func :: [a -> b] -> a -> [b]

Let's compile it:

test.hs:4:9:
Couldn't match type ‘b’ with ‘[b]’

Ah, so in line 4 (originally line 3 because I added the type signature), we have type b but we need a list! Makes sense, no? So that line should really be:

[(head xs) y]   -- originally just `(head xs) y`

Problem 1 fixed, let's compile again:

test.hs:6:9:
Couldn't match type ‘b’ with ‘a -> b’
  ‘b’ is a rigid type variable bound by
      the type signature for func :: [a -> b] -> a -> [b] at test.hs:1:9

Ah, so we would like to have type b but we actually find a -> b which is a function. Makes sense! You forgot to actually apply the value to the function. So that line should be

(head xs) y : func (drop 1 xs) y   -- instead of `(head xs) : func (drop 1 xs) y`

Now let's try again:

Ok, modules loaded: Main.

Fixed!

Now, let's think about how we could write this more idiomatically in Haskell:

I guess, one option would be pattern matches, they are nicer than using the unsafe head function:

func' :: [a -> b] -> a -> [b]
func' fs x =
    case fs of
      f:[] -> [f x]
      f:fs' -> f x : func' fs' x

But actually we might realise, that we just want to map something on every value (in this case a function), so this should really work:

func' :: [a -> b] -> a -> [b]
func' xs y = map SOMETHING xs

And SOMETHING is something that given a function should apply that function with y . Well, that's quite simple: \\f -> fy will, given a function f , apply it with y . So

func' :: [a -> b] -> a -> [b]
func' xs y = map (\f -> f y) xs

does the job just fine. Or in point free style:

func' :: [a -> b] -> a -> [b]
func' xs y = map (flip ($) y) xs

The ($) function has signature (a -> b) -> a -> b and we need exactly that but with the first two arguments flipped (so a -> (a -> b) -> b ) which can be achieved by flip ($) .

I hope that makes sense, otherwise just add a comment.

Hopefully correct version

I misunderstood the question unfortunately, so let's try again: As of the comments, the type signature should be func :: [a -> a] -> a -> a . Let's compile then:

test2.hs:7:3:
Couldn't match expected type ‘a’ with actual type ‘[a]’

Ah, fair enough, in the line (head xs) y : func (drop 1 xs) y we are returning a list but actually we just want a value. So that's easily fixed as we don't want to call the second function with the original y but with first_function y . So let's change that to

func (drop 1 xs) ((head xs) y)

and then it already works :).

Let's also try to make that a bit more idiomatic: So if we find a call func [f1, f2, f3, f4] y what we actually want to execute is (f4 (f3 (f2 (f1 y)))) . And the whole thing already really looks like a fold. And it is!

import Data.List (foldl') 
func' :: [a -> a] -> a -> a
func' xs y = foldl' (\x f -> f x) y xs

alternatively again

func' xs y = foldl' (flip ($)) y xs

In case you wonder why I use foldl' and not foldl , read here why foldl is broken and shouldn't be used .

If I have understood correctly, you want to take as input a list of functions and a point, and apply all these function to that point. For example, given a list of three functions we want to have

func [f,g,h] x = f (g (h x))

The above example can be rewritten using the function composition operator as

func [f,g,h] x = (f . g . h) x

which can then be simplified to, thanks to eta-contraction,

func [f,g,h] = f . g . h

which clarifies that the problem is: given a list of functions, return their composition.

Now, consider a different problem: if we wanted to solve (the generalization of)

anotherFunc [a,b,c] = a + b + c

we would use either recursion

anotherfunc []     = 0
anotherfunc (x:xs) = x + anotherfunc xs

or a fold

anotherFunc xs = foldr (+) 0 xs

Note that anotherFunc exploits a binary operation (+) and its neutral element 0 (ie an element for which x + 0 = 0 + x = x ).

For the original problem (composing a list of functions) we can do the same:

func xs = foldr (.) id xs

Indeed, the identity function id is the neutral element of composition. We can even eta-contract the above to

func = foldr (.) id

or, if preferred, use explicit recursion

func []     y = y
func (x:xs) y = x (func xs y)
  -- or equivalently (x . func xs) y

Another way of looking at this:

After realising that the type you want should be [a -> a] -> a -> a . Adding redundant parentheses to change the emphasis we get [a -> a] -> (a -> a) ; another way of looking at your function is that it takes a list of functions and "condenses" them down to single function.

That sounds extremely similar to function composition, and it is! You're basically specified composition of a list of functions instead of the . operator which composes two of them; we just want to "keep composing all of the functions until there's only one left". Now it sounds very much like a fold:

func' :: [a -> a] -> (a -> a)
func' = foldr (.) _

But what do we fill in the blank with? GHC actually tells us (at least for GHC >= 7.8)!

foo.hs:3:19:
    Found hole ‘_’ with type: a -> a
    Where: ‘a’ is a rigid type variable bound by
               the type signature for func' :: [a -> a] -> a -> a at foo.hs:2:10
    Relevant bindings include
      func' :: [a -> a] -> a -> a (bound at foo.hs:3:1)
    In the second argument of ‘foldr’, namely ‘_’
    In the expression: foldr (.) _
    In an equation for ‘func'’: func' = foldr (.) _

Concentrate on the Found hole '_' with type: a -> a bit.

If you've been doing this a while you'll know that the only reasonable function with type a -> a for any a is id , so that seems like a good candidate. Thinking about it from your specification also leads to id ; the "starting value" of a fold over function composition must be a function to be composed with all the others; we don't want it to do anything, so id makes sense. It's also the function that will be applied to the value if our list of functions is empty (eg func [] x ) - "applying no functions to a value" sounds like it should just leave it unchanged, so again id fits the bill.

So 1 :

func' :: [a -> a] -> (a -> a)
func' = foldr (.) id

Why do I write func' ? Because this isn't your func ; you specified that the value should first pass through the first function in the list, then the second, and finally the last. This means the way to get the effect of f1 (f2 (f3 (f4 x))) out of your func is to write func [f4, f3, f2, f1] x ; the left-to-write order of the functions when you write out the full application is the opposite of the left-to-write order of the functions in the list. . composes in the opposite order, and so does building our list compose by folding with . : func' [f1, f2, f3, f4] x = f1 (f2 (f3 (f4 x))) .

But that's an easy fix; we can just pipe the list of functions through reverse before feeding it to the fold! Which give us:

func :: [a -> a] -> (a -> a)
func = foldr (.) id . reverse

Example:

λ func [("f1 . " ++), ("f2 . " ++), ("f3 . " ++)] "id $ x"
"f3 . f2 . f1 . id $ x"

1 "But wait!" I hear you cry, "doesn't function composition form a monoid with id as the identity element?". Mathematically yes it does, and so actually this function "could" just be: func' = mconcat . But that Monoid instance doesn't actually exist in Haskell, because it requires extentions to write and would overlap with another Monoid instance which does exist in the prelude. In Data.Monoid there's a newtype wrapper Endo a (wrapping a -> a ), which does have the instance.

So you could write func' = appEndo . mconcat . map Endo func' = appEndo . mconcat . map Endo func' = appEndo . mconcat . map Endo , but at that point it's debatable that we're being "simpler" than the fold by exploiting the monoid structure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM