Haskell - 类型包装器统一化

3
我有一堆函数,例如:
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”和“def”,而且在调试时浪费的时间不可估量。我希望他像这样传递参数:fk (A "abc") (B "def")。 据我所见,有两个选项:
  1. Massive data construction and massive unpack function:

    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
    

    Lots of code.

  2. Common typeclass and newtypes:
    EDIT: Okay then, we can use GeneralizedNewtypeDeriving in such simple case.

      {-# 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
    
      ...
    

    Looks much better but all fk would look like

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

    Lots of code and duplication.

我想要的是一些魔术技巧,它可以让我做到以下几点:
  1. fk a b ... z = g [("a", a), ("b", b), ... ("z", z)]
  2. g = h . map (second unpack)
1个回答

2

我认为问题归结为:列表只能有相同类型的元素;这意味着你要么在f中将其合并成单一类型,要么就不能依赖于Haskell的类型检查。例如,以下代码可以正常工作,但类型检查是运行时进行的:

{-# 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)其实没太大区别。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接