General type conversions in Haskell

I am trying to write a arrow transformer that takes regular functions and turns them into calculations by abstract values. If we have an arrow "source",

f :: Int -> Int fx = x + 1 

then the goal would be to work on canceled abstract value types [sic?], in this example

 f' :: AV Int -> AV Int f' (Const x) = Const (fx) -- pass along errors, since AV computation isn't always defined -- or computable in the case of errors f' (Error s) = Error s -- avRep = "abstract representation". Think of symbolic math manipulation or ASTs. f' (Abstract avRep) = AVRepPlus avRep (AVRepConst 1) 

However, in order to successfully implement this arrow, you need to raise several types, so that at an arbitrary depth you have heterogeneous data structures with some specific and some abstract values . What I ended up with is adding special types for regular haskell constructors, for example. if

 g = uncurry (+) -- ie g (x, y) = x + y 

then I add an abstract representation for (,), the constructor of the tuple,

 AVTuple :: AV a -> AV b -> AV (a, b) 

and the code for g is raised to [slightly expanded],

 g' (AVTuple (AVConst a) (AVConst b)) = (AVConst (g (a, b))) g' (AVTuple (AVError e) _) = (AVError e) -- symmetric case here, ie AVTuple _ (AVError e) g' (AVTuple a@ (AVTuple _ _) b) = -- recursive code here 

You need to do the same with AVE. This will end up being a lot of cases. Is there a good way around this?

I am new to Haskell, so please send me links or a semi-detailed explanation; probably the closest I read is SYBR articles (a waste of your turnover) 1-3.

Thank you so much!

+4
source share
2 answers

Let me understand if you understand what you are doing here. You have type AV a , which describes a computation that produces something like a , where the structure of this computation can be saved in such a way as to allow validation. You need a way to raise arbitrary functions in operations on AV , preserving the structure, without creating special cases for each operation.

Usually, Functor and Applicative can be used to raise functions into a structure. However, a simple way to do this involves transforming the structure and directly applying the raised function, rather than saving the application of the function as part of the structure.

What you want is much more inconvenient, and here's why:

Let's say we have some function that we want to raise, and two abstract values ​​of the corresponding type, to apply them to:

 x :: AV A x = ... y :: AV B y = ... f :: A -> B -> C f = ... 

Suppose that there is a liftAV2 function that does what you want. We expect the type lift2 f be AV A -> AV B -> AV C , just like liftA for Applicative .

Later we want to check the calculations obtained with lift2 f , restoring the values ​​of f , x and y . Let's say that at the moment we just want to extract the first argument. Suppose that there exists a function extractArg1 such that extractArg1 (liftAV2 fxy) = x . What is the type of extractArg1 ? Here, in the context, we know that it must be of type AV C -> AV A But what would he be? Something like AV C -> AV A ? This is wrong, because the result is not only a type a , it is irrespective of what type was used to construct the value of AV c . Assuming that the value we are working on was built using the result of liftAV2 f , we know that the type in question exists, but we cannot find it at all.

Here we enter a country that is sufficiently suitable, of existential types . Honestly use them, however, and not just misuse them with class types, as is often the case.

You can, perhaps, accomplish what you need with some effort, but this happens in a fairly advanced area. You want to use GADTs for beginners, although I think you can already do this. It also tends to be extremely awkward when working with existential types, because you are limited only by the fact that they are in limited contexts.

In your particular case, it would be easier to give AV two types of parameters: one of which represents the final type of calculation, and the other represents the structure of the calculation, for example:

 data f :$ x = ... data AV structure result where ... AVApply :: AV f (a -> b) -> AV xa -> AV (f :$ x) b 

Then, to check the calculations, you can look at the first type to find out what you have; to build the calculations, you can look at the second one to ensure type matching. The evaluation function will be of type AV ta -> a , discarding the structure. You can also “unzip” the calculations using the structure type, throwing out the result type if you need to split the structure so that, say, it is enough to print it.

+1
source

As I like to think about it, I would use a Functor instance when I want to talk about some “data with a little extra” (depending on what “a little extra” is, I could actually be talking about Applicative or Monad )

On the other hand, I use an Arrow instance to talk about “functions with a little less”: arrows allow you to define things that can be composed together just like functions, but you can add additional structure or restrictions to prohibit certain constructions ( e.g. arrows without ArrowChoice or ArrowLoop ).

It's not entirely clear what you want to accomplish, but it looks like you are actually wrapping your data in constructors such as AV . In this case, you probably want to make an AV instance of Functor and add Functor instances for (AV a, AV b) => AV (a, b) and similarly for AV wrapped around Either .

0
source

All Articles