问题
Let's say I'm creating a data pipeline which will process text files. I have the following types and functions:
data A = A deriving (Show, Typeable)
data B = B deriving (Show, Typeable)
data C = C deriving (Show, Typeable)
data D = D deriving (Show, Typeable)
step1 :: A -> B
step2 :: B -> C
step3 :: C -> D
For each of the functions step{1..3} below I would like to be able to do produce a new file from an existing file, doing something like:
interact (lines . map (show . step . read) . unlines)
I then want to be able to arrange them into a graph (so functions can have multiple inputs) to achieve the following:
I can traverse the data structure to tell which functions provide inputs to which others
The data structure will type check at compile time so that any invalid arrangement throws a type error.
I know how to do 1 without 2 (give them a common typeclass), and I know how to do 2 without 1 (just use (.)), but I'm not sure how to do both at once. Any ideas? TIA.
Update
AndrewC's answer is pretty much spot on and gets me most of the way there, since I can then build the graph of metadata (names) using a Map and type check it separately at the same time, however I also need some flexible polymorphism. The example below solves this but with 2 serious caveats:
- I'm not forced to be exhaustive in pattern matching in step4.
- It's tedious; there could be dozens of these "BorC" style polymorphic types, which do nothing except enable polymorphism, and not even very safely (see 1). I tried to pattern match on members of a type class (
Step4Input
with instances forB
andC
) but that didn't work (it saidCouldn't match type B with C
).
data BorC = ItsB B | ItsC C
step4 :: BorC -> D
step4 x = case x of { ItsB b -> D; ItsC c -> D }
-- step1 step2
-- \ /
-- ItsB ItsC
-- \ /
-- step4
回答1:
I may be misunderstanding your problem, but why not simply wrap the functions up with a name?
It seems a very straightforward approach to the situation which doesn't require a lot of messing about.
module NamedFunctions where
import Control.Category -- so I can generalise (.)
import Prelude hiding ((.),id) -- don't worry, available from Category
Store a list of function names you've composed along with the function itself:
data Fn name a b = Fn {names::[name],apply:: (a -> b)}
When you show a function, just show what you composed to get there:
instance Show name => Show (Fn name a b) where
show f = show $ names f
Define new versions of (.)
and ($)
:
infixr 9 ...
(...) :: Fn name b c -> Fn name a b -> Fn name a c
f ... g = Fn (names f ++ names g) (apply f . apply g)
infixr 0 $$$
($$$) :: Fn name a b -> a -> b
f $$$ x = apply f x
and reclaim (.)
to work on named functions
instance Category (Fn name) where
(.) = (...)
id = Fn [] id
You can leave functions you don't care about unnamed
so they don't contribute to the names
list.
name n f = Fn [n] f
unnamed f = Fn [] f
You can use whatever data you like to name each function, for example:
timesThree = name "(*3)" (*3)
addFour = name "(+4)" (+4)
step1 :: Fn Int A B
step1 = name 1 undefined
step2 :: Fn Int B C
step2 = name 2 undefined
step3 :: Fn Int C D
step3 = name 3 undefined
*NamedFunctions> timesThree . addFour
["(*3)","(+4)"]
*NamedFunctions> timesThree . addFour $$$ 5
27
*NamedFunctions> step3.step2.step1
[3,2,1]
*NamedFunctions> step3.step2.step1 $$$ A
*** Exception: Prelude.undefined
来源:https://stackoverflow.com/questions/17762311/type-checked-decomposable-graph-in-haskell