1
votes

I have a simple function, and the desire to make sense of point-free style.

shout :: String -> String
shout input
  | null input = []
  | otherwise = (toUpper . head $ input) : (shout . tail $ input)

My intuition led me to this

pfShout :: String -> String
pfShout = (toUpper . head) : (shout . tail)

which is complaining about the following for the first argument of the cons cell

Couldn't match expected type 'String -> String' with actual type '[[Char] -> Char]'

  • Possible cause: '(:)' is applied to too many arguments

    In the expression: (toUpper . head) : (pfShout . tail)

    In an equation for 'pfShout': pfShout = (toUpper . head) : (pfShout . tail)

and complaining about this for the second argument of the cons cell

Couldn't match expected type '[[Char] -> Char]' with actual type '[Char] -> String'

  • Probable cause: '(.)' is applied to too few arguments

    In the second argument of '(:)', namely '(pfShout . tail)'

    In the expression: (toUpper . head) : (pfShout . tail)

    In an equation for 'pfShout': pfShout = (toUpper . head) : (pfShout . tail)

It's clear to me that I can't make a list out of 'String -> String' functions and '[[Char]->Char]', and I'm starting to get a to a place where I'm thinking this just isn't gonna work point-free.

I understand there are other considerations here (like now I'm missing a base-case), but . I also understand I could completely re-write the function to achieve the same effect (like map toUpper). I'm primarily interested in point-free using recursion with the function as it is written.

If it is (or isn't) possible to write this function point-free, what am I missing?

3
Would shout = map toUpper work?n. 1.8e9-where's-my-share m.
oh, there are definitely ways to tweak it entirely, but I was wondering if i could make the recursive version point free (as I type this, I'm thinking "maybe recursion is why I can't point-free"). Shoulda made that more explicit in my questionMatt
Assume you have managed to fix (toUpper . head) : (shout . tail) (it is possible). How does the recursion end? You need to evaluate some condition. How do you write a condition in a point-free manner?n. 1.8e9-where's-my-share m.

3 Answers

8
votes

As @n.m noted you can use shout = map toUpper. However it is possible to do this without map or any other fancy functions like foldr, but we need more combinators. We need something that takes our input argument and passes it to two functions toUpper . head and shout . tail and then combines them with :. You propably don't know this function yet, but the <*> operator from applicative has what we need:

(f <*> g) x = f x (g x)

Now we can do something like this:

combine . f <*> g = \x -> combine (f x) (g x) -- [1]

I will let you figure out how exactly to apply this to your problem. ;)

But we still need to express the empty list case somehow. There are multiple ways to do this but the easiest would be the bool from Data.Bool function which is like an if function, together with join from Control.Monad.

-- [2]
bool x _ False = x
bool _ x True  = x

join f x = f x x

Now we can do the following:

shout = join $ bool (not null case) (null case) . null
-- Which translates to
shout xs = bool ((not null case) xs) ((null case) xs) (null xs)

Again implementing the two cases is left as an excercise to the reader.

[1]: Instead of (.) you could also use (<$>) which for functions is the same as (.) but (<$>) and (<*>) kind of belong together. You will understand why once you learn about applicatives.

[2]: If you wonder what the reasoning behind the order of the arguments of bool is, the first argument is the False case because Bool is defined like this:

data Bool = False | True

And this order is motivated by the convention that False < True. maybe and either are two other functions that share this exact pattern with bool.

2
votes

To rewrite anything in a pointfree style, install pointfree or use any one of the online versions (http://pointfree.io or https://blunt.herokuapp.com).

Your expression

\input -> (toUpper . head $ input) : (shout . tail $ input)

translates to

ap ((:) . toUpper . head) (shout . tail)

(You can substitute <*> for ap, they are interchangeable in this case).

But this is not enough. You also need to somehow end the recursion. To do that in the pointfree style you need a pointfree if or a pointfree pattern match which do not seem to exist in Haskell. In theory if could be defined as a built-in function, which would make a pointfree definition possible, but in Haskell it isn't. (One can define such a function, but its implementation would not be pointfree. So you can trade map for Data.Bool.bool, but is there a point?

Combinators like . and $ and even ap are probably not internally pointfree either, but using them doesn't feel like cheating. They only deal with functions, and thus feel somehow more fundamental than bool or map.

1
votes

The point-free way to do recursion is to use Data.Function.fix, which is explained here: https://en.wikibooks.org/wiki/Haskell/Fix_and_recursion