如何在Haskell函数内指定类实例?类似Rust trait用法
Great question! First, let's fix a small issue with your original Foo class definition—it's not valid Haskell as written because the type variable b isn't properly bound. We need to tie b to the class parameter a using either associated types (the more idiomatic modern approach) or a multi-parameter class with functional dependencies. Let's cover both, then show how to explicitly specify instances when composing f and g.
Step 1: Fix the Foo Class
First, let's rewrite Foo to be valid. Here are two common approaches:
Option A: Associated Type (Recommended)
Use TypeFamilies to define a type associated with a:
{-# LANGUAGE TypeFamilies #-} class Foo a where -- Define a type B a that's tied to each instance of Foo for a type B a g :: B a -> a f :: a -> B a
Option B: Multi-Parameter Class with Functional Dependency
Use FunctionalDependencies to enforce that b is uniquely determined by a:
{-# LANGUAGE FunctionalDependencies #-} class Foo a b | a -> b where g :: b -> a f :: a -> b
For both examples, let's define sample instances to work with:
-- For Option A: instance Foo Int where type B Int = String g = read -- String -> Int f = show -- Int -> String instance Foo Bool where type B Bool = Int g = (>0) -- Int -> Bool f = \x -> if x then 1 else 0 -- Bool -> Int -- For Option B: instance Foo Int String where g = read f = show instance Foo Bool Int where g = (>0) f = \x -> if x then 1 else 0
Step 2: Explicitly Specify the Instance for h
Note: Your original h = f . g would have type b -> b (since f takes a to b, g takes b to a—composing them gives b -> b). To get h :: a -> a, you want g . f instead (composing g after f gives a -> a).
Here are three ways to explicitly choose which Foo instance to use, no Template Haskell needed:
Method 1: Type Applications (Modern & Concise)
Enable the TypeApplications extension, then specify the instance's type parameter directly using @:
{-# LANGUAGE TypeApplications #-} -- For Option A (associated types): hInt :: Int -> Int hInt = g @Int . f @Int -- Uses the Foo Int instance hBool :: Bool -> Bool hBool = g @Bool . f @Bool -- Uses the Foo Bool instance -- For Option B (multi-parameter FD): hInt' :: Int -> Int hInt' = g @Int . f @Int -- FD lets us omit the b parameter
This is analogous to Rust's fully qualified syntax (<Type as Trait>::method), but more concise.
Method 2: Fully Qualified Names with Type Annotations
If you prefer not to use TypeApplications, you can use fully qualified names and type annotations to disambiguate:
-- For Option A: hInt :: Int -> Int hInt = (Foo.g :: String -> Int) . (Foo.f :: Int -> String) -- For Option B: hInt' :: Int -> Int hInt' = (Foo.g :: String -> Int) . (Foo.f :: Int -> String)
Method 3: Proxy Helper Function
You can create a helper that takes a Proxy for your type to explicitly select the instance:
data Proxy a = Proxy -- For Option A: hFor :: Foo a => Proxy a -> (a -> a) hFor _ = g . f hInt :: Int -> Int hInt = hFor (Proxy :: Proxy Int) -- Or hFor (Proxy @Int) with TypeApplications
Bonus: Automatic Instance Inference
If your h function has a concrete type signature (like Int -> Int), GHC will automatically pick the correct Foo instance as long as there's only one instance for that type. For example:
hInt :: Int -> Int hInt = g . f -- GHC infers this uses Foo Int
This works because there's no ambiguity—only one Foo instance exists for Int.
内容的提问来源于stack exchange,提问作者pricks

