Expandable syntax for constrained arrow
Consider a free arrow in which the only task is to remember how it was built. And I want to put some constraint (ex Show) on the arrow arguments:
data FreeArrow a b where
LeafArrow :: (Show a, Show b) => FreeArrow a b
SeqArrow :: (Show a, Show b, Show c) => FreeArrow a b -> FreeArrow b c -> FreeArrow a c
ParArrow :: (Show a1, Show b1, Show a2, Show b2) => FreeArrow a1 b1 -> FreeArrow a2 b2 -> FreeArrow (a1, a2) (b1, b2)
deriving instance Show (FreeArrow a b)
To define an Arrow instance for a FreeArrow, we need to define the constrained Category and Arrow versions:
class Category cat where
id :: Show a => cat a a
(.) :: (Show a, Show b, Show c) => cat b c -> cat a b -> cat a c
class Category a => Arrow a where
arr :: (Show b, Show c) => (b -> c) -> a b c
first :: (Show b, Show c, Show d) => a b c -> a (b,d) (c,d)
second :: (Show b, Show c, Show d) => a b c -> a (d,b) (d,c)
(***) :: (Show b, Show c, Show b', Show c') => a b c -> a b' c' -> a
(&&&) :: (Show b, Show c, Show c') => a b c -> a b c' -> a b (c,c')
There are instances:
instance Category FreeArrow where
id = LeafArrow
(.) = flip SeqArrow
instance Arrow FreeArrow where
arr _ = LeafArrow
first f = ParArrow f id
second f = ParArrow id f
(***) = ParArrow
The approach works well for handwritten arrows:
myArrow :: FreeArrow Double Double
myArrow = (a &&& a) >>> arr (uncurry (+))
where a :: FreeArrow Double Double
a = LeafArrow
main :: IO ()
main = print myArrow
This will print:
SeqArrow (SeqArrow LeafArrow (ParArrow LeafArrow LeafArrow)) LeafArrow
But for arrow syntax (GHC 7.8.3):
myArrow :: FreeArrow Double Double
myArrow = proc v -> a -< v
where a :: FreeArrow Double Double
a = LeafArrow
main :: IO ()
main = print myArrow
I got errors like:
/home/ncrashed/dev/haskell/ArrowProblem.hs:54:11:
No instance for (Show c) arising from a use of βarrβ
Possible fix:
add (Show c) to the context of
a type expected by the context: (b -> c) -> FreeArrow b c
In the expression: arr
When checking that βarrβ (needed by a syntactic construct)
has the required type: forall b1 c1. (b1 -> c1) -> FreeArrow b1 c1
arising from a proc expression
at /home/ncrashed/dev/haskell/ArrowProblem.hs:1:1
In the expression: proc v -> a -< v
/home/ncrashed/dev/haskell/ArrowProblem.hs:54:11:
No instance for (Show c) arising from a use of β>>>β
Possible fix:
add (Show c) to the context of
a type expected by the context:
FreeArrow a b -> FreeArrow b c -> FreeArrow a c
In the expression: (>>>)
When checking that β(>>>)β (needed by a syntactic construct)
has the required type: forall a1 b1 c1.
FreeArrow a1 b1 -> FreeArrow b1 c1 -> FreeArrow a1 c1
arising from a proc expression
at /home/ncrashed/dev/haskell/ArrowProblem.hs:1:1
In the expression: proc v -> a -< v
/home/ncrashed/dev/haskell/ArrowProblem.hs:54:11:
No instance for (Show d) arising from a use of βfirstβ
Possible fix:
add (Show d) to the context of
a type expected by the context:
FreeArrow b c -> FreeArrow (b, d) (c, d)
In the expression: first
When checking that βfirstβ (needed by a syntactic construct)
has the required type: forall b1 c1 d1.
FreeArrow b1 c1 -> FreeArrow (b1, d1) (c1, d1)
arising from a proc expression
at /home/ncrashed/dev/haskell/ArrowProblem.hs:1:1
Is there a way to fix this? This is mistake?
Maybe I should go back to the arrowp preprocessor ...
PS Here is a full sample of the code
source to share
What is the problem:
When you use Haskell's arrow notation, it doesn't naively desugar proc v -> x -< y
into a literal text arr (\v -> y) >>> x
(using everything arr
and >>>
in scope), but rather uses the true values it expects, effectively desalting:
Control.Arrow.arr (\v -> y) Control.Arrow.>>> x
The problem is precisely that when you are faced with a problem - let it pretend it's a house with a closed doorway - you decided to build your own house next to it, the door of which you could open perfectly (defining your own Arrow
and Category
), and now you are trying to manage a remote controlled car that is hopelessly stuck in another house and you are confused because the doorways look very similar, so why is there no RC car upstairs in this house where all your commands are easily redirected? The answer is that to complete this approach you will need to build your own RC car and controller (Haskell source-to-source transformer).
Clearly an easier way is to drop all this work and instead look at a doorknob on a different house.
How to fix the code
Let me explain how walls and doors, which are Haskell-like classes, work. You are advanced enough programming that this might be an overview, so I apologize if this is too short. For the class type Show
, for example, you create the Show
Dictionary function ( Show
, showsPrec
, showList
) for the type constructor, and these functions can use other functions emanating from the other restrictions on the type of class options.
The compiler then stores a dictionary Show
that takes a type constructor to a pair: first, a list of constraints for the type parameters, and second, an actual dictionary that you have implemented, potentially using functions from earlier constraints.When the compiler resolves Show (MyType ParamA ParamB)
, it looks MyType
in the dictionary Show
. finds an instance, checks that ParamA
and ParamB
satisfies any constraints they must fulfill (getting helper function dictionaries for these classes), and builds a function dictionary Show
. Hope that's mostly an overview; if not, go ahead and study a tutorial on how Haskell classroom classes work.
This means you don't need Show
the constructors you write. They are constructors! They don't do anything other than the values ββof the glue, in such a way that you can compare them to each other in different ways - they don't need functions Show
or showsPrec
. Without them, you can still write:
data FreeArrow a b where
LeafArrow :: FreeArrow a b
SeqArrow :: FreeArrow a t -> FreeArrow t b -> FreeArrow a b
ParArrow :: FreeArrow a1 b1 -> FreeArrow a2 b2 -> FreeArrow (a1, a2) (b1, b2)
deriving instance Show (FreeArrow a b)
instance Category FreeArrow where id = LeafArrow; (.) = flip SeqArrow
instance Arrow FreeArrow where
arr = const LeafArrow
first = flip ParArrow id
second = ParArrow id
(***) = ParArrow
In fact you can now use Show
for an arbitrary arrow,
*Main> show (LeafArrow :: FreeArrow () (IO String))
"LeafArrow"
whereas with your code above, you couldn't even create that value because the constructor LeafArrow
required an instance Show
for IO String
which can't provide one. And your target code works easy:
*Main> :set -XArrows
*Main> print $ proc v -> (LeafArrow :: FreeArrow Double Double) -< v
SeqArrow LeafArrow (SeqArrow LeafArrow LeafArrow)
We can do all of this because your information about how it was created doesn't actually use the class Show x
to define an instance Show x
for any of its instances.
How to do what I think you tried to do
The only reason I can "fix" the above is because you don't go into the depth of the parameters involved - which I think you want from the type class Show
. You actually need to define a completely different class type to get this type of information available from the Haskell runtime (unless I'm wrong):
class TypeOf t where
-- the integer here is 10 for type-constructor application, otherwise it is
-- the precedence of the enclosing type-construction-operator.
typePrec :: Int -> t -> String -> String
typePrec _ t = (typeof t ++)
typeof :: t -> String
typeof t = typePrec 0 t "" -- starts at 0 if we have no other info.
instance TypeOf Int where typeof _ = "Int"
instance (TypeOf x) => TypeOf [x] where
typeof list = "[" ++ typeof (head list) ++ "]"
instance (TypeOf x, TypeOf y) => TypeOf (x, y) where
typeof x = "(" ++ typeof (fst x) ++ ", " ++ typeof (snd x) ++ ")"
-- Some helper functions for precedence parsing:
arg f x = typePrec 10 (f x)
pIf m n expr | m <= n = ('(' :) . expr . (')' :)
| otherwise = expr
a <**> b = a . (' ' :) . b
infixr 1 <**>
instance (TypeOf x) => TypeOf (IO x) where
typePrec n x = pIf 10 n $ ("IO" ++) <**> arg io x
where io = undefined :: IO x -> x
instance (TypeOf x, TypeOf y) => TypeOf (Either x y) where
typePrec n x = pIf 10 n $ ("Either" ++) <**> arg left x <**> arg right x
where left = undefined :: Either x y -> x
right = undefined :: Either x y -> y
First, there might be a way to write a C-based extension for Haskell that instead provides typeof :: x -> String
for all x in Haskell; then you could hypothetically get around by simply adding a string parameter to your arrows. Let me pass this opportunity.
The biggest problem above is (.)
from Control.Category
, which explicitly denies access to the inner type b
when composed cat a b
with cat b c
. Overcoming this will be very difficult. You cannot inject any phantom data through cat
if you cannot protect cat
against type changes.
The second problem is that you are really trying to annotate an existing arrow with metadata and not define your own arrow to explore the structure. Along these lines, you can explore this crazy way:
Prelude Control.Arrow Control.Monad.Free Control.Monad.Identity> :set prompt "ghci> "
ghci> type MyArrow = Kleisli Identity
ghci> let x = arr (3*) :: Kleisli (Free (ArrowMonad MyArrow)) Int Int
ghci> :t x
x :: Kleisli (Free (ArrowMonad MyArrow)) Int Int
typeof
What we're basically looking for here is to somehow "traverse" the tree that is implemented here by the monad Free
.
There are obvious restrictions preventing us from saying "this is only an arrow if both its input and its implementation typeof
" (namely, arr
will not have the necessary restriction typeof
), so these types cannot be hidden as a string in the constructor; they also cannot be hidden in the phantom type, because it says cat b c -> cat a b -> cat a c
it is the only two obvious ways to store intermediate types). But as far as I can see, there is no obvious restriction on having a class that at the end of constructing our values ββis built from things that simultaneously implement Arrow
and typeof
.
source to share