简体   繁体   中英

Haskell - type wrappers unification

I have a bunch of functions like:

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)]

So user can just call them like f1 "abc" "def" . I don't want him to do this because he can easily swap "abc" and "def" by mistake (and God knows how much time would be wasted while debugging). I want him to pass arguments like fk (A "abc") (B "def") As far as I can see, there are 2 options:

  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 ab ... z = g [("a", unpack a), ("b", unpack b), ... ("z", unpack z)] 

    Lots of code and duplication.

What I want is some magic trick which would allow me:

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

I think the problems boils down to this: The list can have elements of the same type only; which means that either you have to 'coalesce' it into a single type in your f , or you cannot rely on haskells type checks. Eg the following code would work for you, but the type check is runtime:

{-# 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")

When you want compile-time type check, you can do something like this; however, you still have to retype the parameters to the same type, so in some sense, there is not much difference between unpacking it, only it might by slightly less error-prone, though. A nice trick comes from the json packages - they redefine some operator (eg =:) to create the type, so you would have:

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

It's not that much different from just defining a =: b = (a, unpack b) though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM