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.