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

Haskell泛型实现千/兆/吉单位转千单位的通用函数

Using GHC Generics to Eliminate Duplicate Conversion Logic

Great question! It's smart to notice that convertToKB and convertToKBs share identical logic—we can absolutely use GHC Generics to abstract this into a single, reusable convertToK function. The key is to inspect the constructor names of your types (via their generic representation) and apply the correct scaling factor based on the K/M/G prefix.

Let's walk through fixing those three "?" spots and building the generic solution step by step:

Step 1: Adjust the ConvertToK' Class

First, we need to modify the ConvertToK' class to accept a scaling factor parameter. This lets us pass the correct multiplier (1, 1000, 1000000) down to the code that modifies the underlying Double value:

class ConvertToK' f where
    convertToK' :: Double -> f p -> f p

The Double here is our scaling factor, and f p is the generic representation of our type.

Step 2: Implement ConvertToK' Instances

We need instances for all the generic building blocks used by your types:

  1. Empty Constructors (U1):
    Our types don't use empty constructors, but we need this instance to satisfy generic constraints:

    instance ConvertToK' U1 where
        convertToK' _ U1 = U1
    
  2. Wrapped Values (K1):
    This handles the actual Double value inside each constructor. We multiply it by the passed-in factor:

    instance ConvertToK' (K1 i Double) where
        convertToK' factor (K1 d) = K1 (d * factor)
    
  3. Constructors (C1):
    This is where we inspect the constructor name to determine the scaling factor. Using the Constructor constraint, we can get the constructor's name, check its first character, and pass the right factor to process the constructor's value:

    instance (Constructor c, ConvertToK' f) => ConvertToK' (C1 c f) where
        convertToK' _ c1@(M1 x) =
            let conPrefix = take 1 (conName c1)
                factor = case conPrefix of
                            "K" -> 1.0
                            "M" -> 1000.0
                            "G" -> 1000000.0
                            _ -> 1.0 -- Fallback for unexpected constructor names
            in M1 (convertToK' factor x)
    
  4. Sum Types (:+:):
    This handles the multiple constructors in your DataAmount and Speed types. We just pass the factor through to each branch and wrap the result back up:

    instance (ConvertToK' f, ConvertToK' g) => ConvertToK' (f :+: g) where
        convertToK' factor (L1 x) = L1 (convertToK' factor x)
        convertToK' factor (R1 x) = R1 (convertToK' factor x)
    

Step 3: Add Default Generic Implementation to ConvertToK

Now we update the ConvertToK class to use our generic logic by default. We use from to convert the original type to its generic representation, process it with convertToK', then use to to convert back:

class ConvertToK a where
    convertToK :: a -> a
    default convertToK :: (Generic a, ConvertToK' (Rep a)) => a -> a
    convertToK x = to (convertToK' 1.0 (from x))

Step 4: Hook Up Your Types

Since DataAmount and Speed already derive Generic, we just need to declare instances for ConvertToK—the default implementation will handle the rest:

instance ConvertToK DataAmount
instance ConvertToK Speed

Full Working Code

Here's the complete code with all these changes, replacing your duplicate conversion functions with the generic convertToK:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
module Lib ( someFunc ) where

import GHC.Generics
import GHC.Generics (Constructor(..))

data DataAmount = KB Double | MB Double | GB Double deriving (Generic, Show)
data Speed = KBs Double | MBs Double | GBs Double deriving (Generic, Show)

class ConvertToK a where
    convertToK :: a -> a
    default convertToK :: (Generic a, ConvertToK' (Rep a)) => a -> a
    convertToK x = to (convertToK' 1.0 (from x))

class ConvertToK' f where
    convertToK' :: Double -> f p -> f p

instance ConvertToK' U1 where
    convertToK' _ U1 = U1

instance ConvertToK' (K1 i Double) where
    convertToK' factor (K1 d) = K1 (d * factor)

instance (Constructor c, ConvertToK' f) => ConvertToK' (C1 c f) where
    convertToK' _ c1@(M1 x) =
        let conPrefix = take 1 (conName c1)
            factor = case conPrefix of
                        "K" -> 1.0
                        "M" -> 1000.0
                        "G" -> 1000000.0
                        _ -> 1.0
        in M1 (convertToK' factor x)

instance (ConvertToK' f, ConvertToK' g) => ConvertToK' (f :+: g) where
    convertToK' factor (L1 x) = L1 (convertToK' factor x)
    convertToK' factor (R1 x) = R1 (convertToK' factor x)

instance ConvertToK DataAmount
instance ConvertToK Speed

timeDiv (KB x) (KBs z) | z>0 = x/z

someFunc :: IO ()
someFunc = do
    putStrLn "Give the amount of data:"
    dat <- readLn
    putStrLn "Give 1 for KB 2 for MB 3 for GB:"
    unit <- readLn
    let dataAmount = case unit of
            1 -> KB dat
            2 -> MB dat
            3 -> GB dat
            _ -> KB dat
    putStrLn "Give speed of data:"
    speed <- readLn
    putStrLn "Give 1 for KB/s 2 for MB/s 3 for GB/s:"
    speedunit <- readLn
    let speedAmount = case speedunit of
            1 -> KBs speed
            2 -> MBs speed
            3 -> GBs speed
            _ -> KBs speed
    -- Use our generic convertToK instead of duplicate functions
    let speedAmountKBs = convertToK speedAmount
    let dataAmountKB = convertToK dataAmount
    let result = timeDiv dataAmountKB speedAmountKBs
    putStrLn $ "You need " ++ show result ++ " seconds"

Key Notes

  • The Constructor constraint gives us access to conName, which lets us get the name of each constructor (like "KB" or "MBs").
  • We use the first character of the constructor name to decide the scaling factor—this works perfectly for your naming pattern.
  • Now you can reuse convertToK for any future types that follow the same K/M/G prefix pattern, just by deriving Generic and declaring a ConvertToK instance.

内容的提问来源于stack exchange,提问作者Dragno

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:39:50