2

I seem to have got myself stuck in an interesting edge-case of the language here. It's tricky to explain what I'm trying to do, so let me write it in code instead:

data Foobar x =
  Foo1 {field1 :: x, field2 :: String} |
  Foo2 {field1 :: x, field3 :: Int} |
  Foo3 {             field4 :: Bool} |
  Foo4 {             field2 :: String, field4 :: Bool}

As you can see, some constructors depend on x, but others do not. I'm trying to write a function similar to fmap:

transform :: (x -> y) -> Foobar x -> Foobar y
transform fn foobar =
  case foobar of
    Foo1 {} -> foobar {field1 = fn (field1 foobar)}
    Foo2 {} -> foobar {field1 = fn (field1 foobar)}
    _       -> foobar

As you can see, record syntax neatly lets me avoid having to rebuild the entire constructor, applying fn only where it is needed. Unfortunately, this breaks when fn is needed in zero places. In that case (i.e., the final alternative), the expression fails to type-check. It is quite clear to me why it fails - but I'm mystified as to how to fix this.

Obviously I could just write out the entire thing long-hand. That would work for this cut-down example, but the real application I'm trying to write is quite a lot larger. (About 25 constructors, some of them with upwards of 15 fields.)

Does anybody have any neat ideas on how I can fix this glitch?

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220

2 Answers2

4

One solution (to save typing) is to use record wild cards:

{-# LANGUAGE RecordWildCards #-}
-- ...
case foobar of
   Foo4 {..} -> Foo4 {..}
Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
  • Ooo, that's interesting... It seems the pragma also turns on some other stuff that I don't want, but if I put this just in the module that defines the transformation functions, it should be safe... – MathematicalOrchid Feb 08 '14 at 18:12
2

You have to pattern match if you want to do this manually unfortunately. If you want to avoid so much work, a simple fix is

   {-# LANGUAGE DeriveFunctor #-}
   data Foo a = ...
     deriving(Functor)

Now we can write a safer form of unsafeCoerce

  coerceFoo :: Foo a -> Foo b
  coerceFoo = fmap (error "This shouldn't be used on a phantom type")

And use this in your example

_       -> coerceFoo foobar
daniel gratzer
  • 52,833
  • 11
  • 94
  • 134