You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:12:01