Reputation: 33911
I am processing a web request, and trying to save a potential entity (User / Address 1). The main question is, how can I transform a list of Strings to parameters to a function - where the list could be of arbitrary length.
I have looked at Passing list elements as parameters to curried function but this seems to be a solution only when knowing the amount of parameters in advance.
data User = User String String
data Address1 = Address1 String
let ufields = ["chris", "str"]
let afields = ["earth"]
I'm looking for a function along the lines of:
f :: [String] -> (? -> c) -> Maybe c
f fields c = undefined
So all I would need to pass is the data constructor (User/Address1), and a list of strings.
Examples:
f ufields User
would return Just ( User "chris" "str")
.
f ["chris"] User
would return Nothing
.
f [] User
would return Nothing
.
f afields Address1
would return Just ( Address1 "earth" )
.
Is this possible to be done without using TemplateHaskell? I can achieve the above manually, but it involves quite a bit of additional typing:
data User = User String String deriving (Show)
data Address1 = Address1 String deriving (Show)
data EntityType = UserEntity | AddressEntity
data EntityContainer = UserContainer User | AddressContainer Address1
f :: EntityType -> [String] -> Maybe EntityContainer
f UserEntity (p:p':[]) = Just $ UserContainer $ User p p'
f AddressEntity (p:[]) = Just $ AddressContainer $ Address1 p
f _ _ = Nothing
printPossibleEntity :: [String] -> EntityType -> IO ()
printPossibleEntity fields entityType =
case (f entityType fields) of
Just (UserContainer u) -> print u
Just (AddressContainer a) -> print a
Nothing -> print "No entity matched"
main :: IO ()
main = do
let ufields = ["chris", "str"]
let afields = ["earth"]
printPossibleEntity ufields UserEntity
printPossibleEntity afields AddressEntity
printPossibleEntity [] AddressEntity
Which outputs:
User "chris" "str"
Address1 "earth"
"No entity matched"
Upvotes: 2
Views: 102
Reputation: 32309
Let me preface this by saying that you should almost certainly not use this.
The usual way of doing this sort of thing is to have overlapping multiparameter typeclasses.
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances,
UndecidableInstances #-}
class PackArgs a b | a -> b where
packArgs :: [String] -> a -> Maybe b
instance {-# OVERLAPPING #-} PackArgs y z => PackArgs (String -> y) z where
packArgs [] _ = Nothing
packArgs (a:as) f = packArgs as (f a)
instance {-# OVERLAPPABLE #-} PackArgs z z where
packArgs (_:_) _ = Nothing
packArgs [] z = Just z
And here it is working in action:
ghci> data User = User String String deriving Show
ghci> data Address1 = Address1 String deriving Show
ghci> packArgs ["chris","str"] User :: Maybe User
Just (User "chris" "str")
ghci> packArgs ["chris"] User :: Maybe User
Nothing
ghci> packArgs [] User :: Maybe User
Nothing
ghci> packArgs ["earth"] Address1 :: Maybe Address1
Just (Address1 "earth")
The problem is that we need the type annotations for this to work. In a nutshell, Haskell needs to know what your expected return type is. We can fix this with some type families.
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances,
UndecidableInstances, TypeFamilies, ScopedTypeVariables #-}
import Data.Proxy
type family StringFuncReturn a where
StringFuncReturn (String -> b) = StringFuncReturn b
StringFuncReturn b = b
class PackArgs a where
packArgs :: [String] -> a -> Maybe (StringFuncReturn a)
instance (StringFuncReturn a ~ r, PackArgs' a r) => PackArgs a where
packArgs = packArgs' (Proxy :: Proxy r)
class PackArgs' a b where
packArgs' :: Proxy b -> [String] -> a -> Maybe b
instance {-# OVERLAPPING #-} PackArgs' y z => PackArgs' (String -> y) z where
packArgs' _ [] _ = Nothing
packArgs' p (a:as) f = packArgs' p as (f a)
instance {-# OVERLAPPABLE #-} PackArgs' z z where
packArgs' _ (_:_) _ = Nothing
packArgs' _ [] z = Just z
And here it is working in action:
ghci> data User = User String String deriving Show
ghci> data Address1 = Address1 String deriving Show
ghci> packArgs ["chris","str"] User
Just (User "chris" "str")
ghci> packArgs ["chris"] User
Nothing
ghci> packArgs [] User
Nothing
ghci> packArgs ["earth"] Address1
Just (Address1 "earth")
Upvotes: 4