1

I'm using esqueleto for making SQL queries, and I have one query which returns data with type (Value a, Value b, Value c). I want to extract (a, b, c) from it. I know that I can use pattern matching like that:

let (Value a, Value b, Value c) = queryResult 

But I'd like to avoid repeating Value for every tuple element. This is particularly annoying when the tuple has much more elements (like 10). Is there any way to simplify this? Is there a function which I could use like that:

let (a, b, c) = someFunction queryResult 
2
  • Have you tried using lenses.? Commented Nov 4, 2017 at 22:27
  • 3
    Are you using the version of esqueleto on Hackage or from that Github link? In the Github one looks like Value is a newtype but the Hackage one has it as a data. If you're using the Github one (with it as newtype), your someFunction is just coerce from Data.Coerce. It has the added benefit of having zero runtime cost as well. Commented Nov 4, 2017 at 22:53

3 Answers 3

2

Data.Coerce from base provides coerce, which acts as your someFunction.

coerce "exchanges" newtypes for the underlying type they wrap (and visa-versa). This works even if they are wrapped deeply within other types. This is also done with zero overhead, since newtypes have the exact same runtime representation as the type they wrap.

There is a little bit more complexity with type variable roles that you can read about on the Wiki page if you're interested, but an application like this turns out to be straightforward since the package uses the "default" role for Value's type variable argument.

Sign up to request clarification or add additional context in comments.

Comments

2

The library appears to have an unValue function, so you just need to choose a way to map over arbitrary length tuples. Then someFunction can become

import Control.Lens (over, each) someFunction = (over each) unValue 

If you want to try some other ways to map tuples without a lens dependency, you could check out this question: Haskell: how to map a tuple?

edit: As danidiaz points out this only works for tuples which are max 8 fields long. I'm not sure if there's a better way to generalise it.

3 Comments

each only works for homogeneous tuples of size less than 10.
@danidiaz Ah, that's a pain. The linked page I think said 10, so I was hoping it would just fit this use case.
Also, homogeneous is important here, I suspect it is a dealbreaker.
2

If your tuple has all the same element type:

all3 :: (a -> b) -> (a, a, a) -> (b, b, b) all3 f (x, y, z) = (f x, f y, f z) 

This case can be abstracted over with lenses, using over each as described in @Zpalmtree’s answer.

But if your tuple has different element types, you can make the f argument of this function polymorphic using the RankNTypes extension:

all3 :: (forall a. c a -> a) -> (c x, c y, c z) -> (x, y, z) all3 f (x, y, z) = (f x, f y, f z) 

Then assuming you have unValue :: Value a -> a, you can write:

(a, b, c) = all3 unValue queryResult 

However, you would need to write separate functions all4, all5, …, all10 if you have large tuples. In that case you could cut down on the boilerplate by generating them with Template Haskell. This is part of the reason that large tuples are generally avoided in Haskell, since they’re awkward to work with and can’t be easily abstracted over.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.