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
Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • Have you tried using lenses.? – Redu Nov 04 '17 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. – David Young Nov 04 '17 at 22:53

3 Answers3

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.

Zpalmtree
  • 1,339
  • 8
  • 14
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.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
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.

David Young
  • 10,713
  • 2
  • 33
  • 47