Haskell中多函数依赖同一参数的部分应用简化方案咨询
Great question! You’re absolutely right that repeating those where clauses to partially apply the shared Par parameter gets tedious. There are several clean ways to eliminate this boilerplate, and I’ll walk you through the most straightforward ones:
1. Applicative Function Composition (Simplest Solution)
Since your functions f and g both take a Par parameter first, we can treat them as values in the (->) Par applicative functor (essentially the reader applicative under the hood).
The standard function composition operator (.) has the type (b -> c) -> (a -> b) -> (a -> c). We can lift this operator to work with our Par-dependent functions using applicative operations:
h :: Par -> a -> c h = (.) <$> g <*> f
Let me break this down:
(.) <$> gtakes eachParvalue, appliesgto get ab -> cfunction, then wraps the composition operator around it—resulting in a functionPar -> ((a -> b) -> (a -> c)).<*> fthen feeds thea -> bfunction fromf parinto that composed function, giving us exactlyg par . f parfor everyParvalue.
This is the most concise approach and requires no extra imports!
2. Custom Higher-Order Combinator
If you want something more explicit (or don’t want to use applicative syntax), you can define a reusable combinator that handles the shared parameter for you:
-- A combinator that composes two functions sharing a leading parameter composeShared :: (p -> b -> c) -> (p -> a -> b) -> p -> a -> c composeShared g f par = g par . f par
Then you can define h in one line without any where clauses:
h :: Par -> a -> c h = composeShared g f
You can reuse this composeShared function for any other pairs of functions that share a leading parameter type, not just Par.
3. Reader Monad (For More Complex Workflows)
If you end up with longer chains of these functions (not just two), the Reader monad (from Control.Monad.Reader) is designed specifically for passing a shared "environment" (your Par value) through a sequence of computations.
First, convert our functions to work with the Reader monad:
import Control.Monad.Reader -- Convert f to a Reader computation: given an 'a', return a Reader that produces 'b' fReader :: a -> Reader Par b fReader a = flip f a <$> ask -- 'ask' retrieves the current Par environment -- Convert g similarly gReader :: b -> Reader Par c gReader b = flip g b <$> ask
Then chain these using the Kleisli composition operator (>=>) (which composes monadic functions), and run the reader with our Par parameter:
h :: Par -> a -> c h par a = runReader (fReader a >=> gReader) par
Or even more concisely, flip the order to match your desired type:
h :: Par -> a -> c h = flip $ runReader . (fReader >=> gReader)
The Reader monad shines when you have multiple steps that all need access to the same Par value—it keeps the parameter passing implicit instead of cluttering up your code.
内容的提问来源于stack exchange,提问作者Stefan Witzel

