Type checked decomposable graph in Haskell

寵の児 提交于 2019-12-01 06:12:18

问题


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:

  1. I can traverse the data structure to tell which functions provide inputs to which others

  2. 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:

  1. I'm not forced to be exhaustive in pattern matching in step4.
  2. 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 for B and C) but that didn't work (it said Couldn'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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!