Haskell类型包装器的统一



我有很多函数,比如:

f1 :: String -> String -> ... -> String -> ()
f1 a b ... z = g [("a", a), ("b", b), ... ("z", z)]
...
fn :: String -> Int -> String -> ... -> String -> ()
fn a b ... z = g [("a", a), ("b", show b), ... ("z", z)]

所以用户可以像f1 "abc" "def"一样调用它们。我不希望他这样做,因为他很容易错误地交换"abc"one_answers"def"(天知道调试时会浪费多少时间)。我希望他通过像fk (A "abc") (B "def")这样的论点就我所见,有两种选择:

  1. 大规模data构建和大规模解包功能:

    data Value = A String
               | B String
               | C Int
               | D String
               ...
     unpack :: Value -> String
     unpack (A a) = a
     unpack (B b) = b
     unpack (C c) = show c
     unpack (D c) = d
    

    很多代码。

  2. 常见类型类和新类型:
    编辑:好的,那么,我们可以在这样简单的情况下使用GeneralizedNewtypeDeriving

      {-# LANGUAGE GeneralizedNewtypeDeriving #-}
      class Value a where
        unpack :: a -> String
      instance Value String where
        unpack = id
      instance Value Int where
        unpack = show
      newtype A = A String deriving Value
      newtype B = B String deriving Value
      newtype C = C Int deriving Value
      newtype D = D String deriving Value
      ...
    

    看起来好多了,但所有fk看起来都像

       fk a b ... z = g [("a", unpack a), ("b", unpack b), ... ("z", unpack z)]
    

    大量的代码和重复。

我想要的是一些魔术,这将允许我:

  1. fk a b ... z = g [("a", a), ("b", b), ... ("z", z)]
  2. g = h . map (second unpack)

我认为问题归结为:列表只能有相同类型的元素;这意味着要么必须在f中将其"合并"为一个类型,要么不能依赖haskells类型检查。例如,以下代码适用于您,但类型检查是运行时的:

{-# LANGUAGE GADTs #-}
import Control.Arrow (second)
data Item where
    A :: String -> Item
    B :: Int -> Item
unpack (A s) = s
unpack (B i) = show i
myf a@(A {}) b@(B {}) c@(B {}) = 
    let g = [("a", a), ("b", b), ("c", c)]
    in map (second unpack) g
myf _ _ _ = error "Bad types"
main = do
    putStrLn $ show $ myf (A "test") (B 13) (B 14)
    putStrLn $ show $ myf (A "test") (B 13) (A "xxx")

当您想要编译时类型检查时,可以执行以下操作;然而,您仍然需要将参数重新键入相同的类型,因此从某种意义上说,拆包与拆包之间没有太大区别,只是可能不太容易出错。json包提供了一个很好的技巧-它们重新定义了一些运算符(例如=:)来创建类型,因此您将拥有:

{-# LANGUAGE ExistentialQuantification #-}
import Control.Arrow (second)
class Value a where
    unpack :: a -> String
newtype A = A String
newtype B = B Int
instance Value A where
    unpack (A a) = a
instance Value B where
    unpack (B b) = show b
data Item = forall b. Value b => Item b
a =: b = (a, Item b)
myf :: A -> B -> B -> [(String, String)]
myf a b c = 
    let g = ["a" =: a, "b" =: b, "c" =: c]
    in map (second ((Item x) -> unpack x)) g
main = do
    putStrLn $ show $ myf (A "test") (B 13) (B 14)

不过,这与仅仅定义a =: b = (a, unpack b)没有太大区别。

最新更新