Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell constructor as variables of functions [duplicate]

I have a type with several constructors wrapping another type; let's use the example below (in practice I have many constructors):

data New a = A a | B a

Now I need a function fun :: (a -> b) -> New a -> b which applies the first argument f :: a -> b to the value wrapped in x :: New a. I can do this with pattern matching:

fun f (A v) = f v
fun f (B v) = f v

This is not elegant at all! It seems like I should be able to do something like fun f (_ v) = f v instead, but GHC gives me Parse error in pattern.

Similarly, I want a function con :: New a -> (a -> New a) which returns the constructor. Again, I can pattern match:

con (A _) = A
con (B _) = B

The obvious pattern to unite these is con (x _) = x, but this throws another Parse error in pattern: x.

Questions:

  1. Is there a shorter, more elegant way of defining fun and con by uniting cases?
  2. Why GHC opposed to these patterns?
  3. Am I trying to do something taboo here?

Note: I have formal training in mathematics, but I am self-taught at programming. I am also somewhat new to haskell--sorry if this question has an obvious, trivial answer. I've tried quite a lot of googling, but I haven't had any success.

like image 943
User12345 Avatar asked Dec 31 '20 18:12

User12345


Video Answer


1 Answers

There is, strictly speaking, no technical reason for GHC not to allow this kind of thing, except that it would only work when all constructors have parameters of the same type - otherwise you couldn't apply the same function to them.

And this gives us an insight: most of the time, discriminated union constructors have parameters of different types, like data Thing = Text String | Number Int, and even if the types happen to be the same, that's usually just a coincidence, and the parameters actually have different meanings, like data Heisenberg = Velocity Vector2D | Position Vector2D, so that it wouldn't make sense to apply the same function to them, even if technically possible.

This is the intended meaning of discriminated unions. The constructors are supposed to represent semantically different kinds of things.

And that's why GHC doesn't support this sort of syntax, even when the types match: it's outside of the intended use case, and most of the time it's useless, even if it could potentially be useful in some very narrow area.


But from your description of desired use cases, it kinda seems like you're trying to express a completely different thing: it looks like the a in both A a and B a is meant to represent the same thing, while A and B are used merely as "tags", to express some attribute of the value, which is not inherent to the value itself. Like, for example, a could be the size of a balloon, while A and B could represent two different colors that balloons can have.

If this is indeed what you're trying to express, then a better model would be to encode the tags as such instead of trying to shoehorn DU constructors to represent them, and then combine the tag with the value in a record:

data NewTag = A | B
data New a = New { tag :: NewTag, value :: a }

With this definition, both fun and con become trivial:

fun :: (a -> b) -> New a -> b
fun f n = f $ value n

con :: NewTag -> a -> New a
con tag value = New { tag = tag, value = value }

Or point-free if you're into that sort of thing:

fun :: (a -> b) -> New a -> b
fun f = f . value 

con :: NewTag -> a -> New a
con = New
like image 98
Fyodor Soikin Avatar answered Sep 25 '22 23:09

Fyodor Soikin