Home > Back-end >  Apply function to argument before performing pattern matching
Apply function to argument before performing pattern matching

Time:10-28

I'm new to Haskell and trying to not instinctively think imperatively. I have a function which does all the work in a pattern matching block, however it needs to pattern match on the argument after a function is applied to it.

Doing this in a functional way gets me to this:

foo :: Int -> String
foo n = bar $ show n

bar :: String -> String
bar [] = ""
bar (c:s) = "-"    bar s

Where foo is the function I'm trying to implement but bar is where all the work gets done. foo only exists to provide the right type signature and perform the precursor show transformation before calling bar. In practice, bar could get quite complicated, but still, I have no reason to expose it as a separate function.

What's the Haskell way to perform a simple function like show and "then" pattern match on the result of that?

I tried changing the pattern matching to a case statement, but it didn't permit the all-important recursion, because there was no function to call recursively. For the same reason, using a where clause applied to multiple patterns also doesn't work.

CodePudding user response:

I remembered that Learn You A Haskell often seemed to emphasise that where and let are more powerful than they first seem because "everything is a function" and the clauses are expressions themselves.

That prompted me to see if I could push where a bit harder and use it to essentially define the helper function bar. Turns out I can:

foo :: Int -> String
foo n = bar $ show n
  where bar [] = ""
        bar (c:s) = "-"    bar s

Unless there's a better way, I think this is the solution I'm after. It took a bit of beating back my imperative tendencies to see this, but it's starting to look logical and much more "core" than I was imagining.

Please provide an alternative answer if these assumptions are leading me off course!

CodePudding user response:

It depends on whether the pattern-matching function is recursive or not. In your example, it is called bar and is recursive.

foo :: Int -> String
foo n = bar $ show n

bar :: String -> String
bar [] = ""
bar (c:s) = "-"    bar s

Here, the (arguably) best solution is the one you found: use where (or let) and define it locally to foo:

foo :: Int -> String
foo n = bar $ show n
   where
   bar :: String -> String   -- optional type annotation
   bar [] = ""
   bar (c:s) = "-"    bar s

The type annotation for the inner function is optional. Many Haskellers think that the top-level function foo should have its signature (and GHC with -Wall warns if you do not provide it) but also believe that the inner function do not have to be annotated. For what it is worth, I like to add it when I think it's non obvious from the context. Feel free to include or omit it.

When bar is not recursive, we have other options. Consider this code:

foo :: Int -> String
foo n = bar $ show n

bar :: String -> String
bar []    = "empty"
bar (c:s) = "nonempty "    c : s

Here, we can use case of:

foo :: Int -> String
foo n = case show n of
   []    -> "empty"
   (c:s) -> "nonempty "    c : s

This calls function show first, and then pattern-matches its result. I think this is easier to read than adding a where to define bar.

Theoretically, speaking, we could follow the case approach even in the recursive case, and leverage fix (a function from the library) to close the recursion. I do not recommend you to do this, since defining bar using where (or let) is more readable. I'm adding this less readable option here only for the sake of completeness.

foo :: Int -> String
foo n = fix (\bar x -> case x of
   []    -> ""
   (c:s) -> "-"    bar s
   ) $ show n

This is equivalent to the first recursive code snippet, but it requires much more time to read. The helper fix has its uses, but if I read this in actual production code I'd think the programmer is trying to show they are "clever" instead of writing simple, readable code.

  • Related