3
\$\begingroup\$

I've recently tried to write my functions by using composition. But this one, renderCross' is especially difficult to convert fully. How would I write this composition point free? It's the n that I'm trying to make disappear. I've tried using <*>, and pointfree.io uses liftA2 (but this results in very ugly code) so I would believe I'm at least somewhat close.

Maybe I could use the Reader monad, but would that be too complicated?

renderCross :: Int -> String
renderCross = spread . unlines . flip renderCross' [""]
  where
    renderCross' :: Int -> [String] -> [String]
    renderCross' n = edge n . vert n . horiz n . mid n . horiz n . vert n . edge n

edge :: Int -> [String] -> [String]
edge i s = s ++ [space i ++ "ooo"]

vert :: Int -> [String] -> [String]
vert i s = s ++ replicate (i - 2) (space i ++ "o|o")

horiz :: Int -> [String] -> [String]
horiz i s = s ++ [horiz' ++ "|" ++ horiz']
  where
    horiz' = replicate i 'o'

mid :: Int -> [String] -> [String]
mid i s = s ++ [intercalate (replicate (i - 1) '-') ["o", "o", "o"]]

space :: Int -> String
space n = replicate (n - 1) ' '

EDIT:

-- spread out word, i.e. add a space between each character
spread = intersperse ' '
\$\endgroup\$
1
  • 1
    \$\begingroup\$ What's the implementation for spread? AFAICT, it's not in base, and I can't find a matching one on Hoogle. Would be helpful for trying out what the function does \$\endgroup\$
    – l7r7
    Commented Jun 29, 2022 at 8:44

1 Answer 1

4
\$\begingroup\$

Yes, you could use the Reader monad to get a pointfree version of renderCross', like this:

renderCross :: Int -> String
renderCross = spread . unlines . renderCross' [""]
  where
    renderCross' ::  [String] -> Int -> [String]
    renderCross' = runReader . (edge >=> vert >=> horiz >=> mid >=> horiz >=> vert >=> edge)

edge :: [String] -> Reader Int [String]
edge s = asks (\i -> s ++ [space i ++ "ooo"])

vert :: [String] -> Reader Int [String]
vert s = asks (\i -> s ++ replicate (i - 2) (space i ++ "o|o"))

horiz :: [String] -> Reader Int [String]
horiz s = asks (\i -> let horiz' = replicate i 'o' in s ++ [horiz' ++ "|" ++ horiz'])

mid :: [String] -> Reader Int [String]
mid s = asks (\i -> s ++ [intercalate (replicate (i - 1) '-') ["o", "o", "o"]])

space :: Int -> String
space n = replicate (n - 1) ' '

This does the following:

  • The functions edge, vert, horiz, and mid now return a Reader Int [String], their implementations use ask to access the Int instead of using a plain function argument
  • renderCross' doesn't use function composition anymore, but (>=>), aka Kleisli composition. It's basically the same, but since you have functions that have a monadic return value, the simple function composition doesn't work anymore. Note that the order of composition is reversed compared to (.). You just don't see it in this example because it's the same forwards or backwards.
  • renderCross' uses runReader to feed in the argument to the composed function. Text-book application of the reader monad.
  • the type of renderCross' is different to your example. I switched the two arguments, to simplify the pointfree implementation. This has the additional benefit that you don't need to flip it in the implementation of renderCross.

So yes, it's possible to write a pointfree version of the function by using Reader. Is it better than the original version? I'm not sure. It's definitely cool to have a pointfree implementation but you have to pay the price of more concepts that are used (Reader, (>=>)). Plus, in my experience, pointfree functions tend to be harder to understand than equivalent implementations with explicit arguments.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.