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

+3


source to share


1 answer


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

.

0


source







All Articles