Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lens package with algebraic types

Tags:

haskell

lenses

I was coding with with the lens package. Everything was going fine until I tried to access a certain field on an algebraic type:

import Control.Lens

data Type = A { _a :: Char } | B

makeLenses ''Type

test1 = _a (A 'a')
test2 = (A 'a') ^. a

No instance for (Data.Monoid.Monoid Char)
  arising from a use of `a'
Possible fix:
  add an instance declaration for (Data.Monoid.Monoid Char)
In the second argument of `(^.)', namely `a'
In the expression: (A 'a') ^. a
In an equation for `test2': test2 = (A 'a') ^. a

I could just go with _a, but the datatype in my real program is much deeper and I kind of intended on using lens to lower the amount of work I have to do. I have been looking over the lens library but there's so much there, and I'm not sure if he's dealt with this scenario or it is just something the lens library doesn't support.

As a side note, if I actually use a monoid like String for the datatype instead of Char, it then compiles and gives the right answer, I have no idea why.

Edit: After reading hammar's comment, I tried this and this works:

test2 = (A 'a') ^? a
test3 = B ^? a

But it is kind of weird to get a maybe out of that for something that has to exist.

like image 792
David McHealy Avatar asked Apr 25 '13 03:04

David McHealy


1 Answers

Just so that this is answered, my problem was that I had an algebraic type where some fields were in common between the different constructors but there was a couple fields that weren't shared would die in runtime if I tried to use them.

data Exercise =
  BarbellExercise {
    name   :: String,
    weight :: Int,
    reps   :: Int
  } |
  BodyWeightExercise {
    name   :: String,
    reps   :: Int
  }

exer1 = BarbellExercise "Squats" 235 15
exer2 = BarbellExercise "Deadlifts" 265 15
exer3 = BodyWeightExercise "Pullups" 12
exer4 = BarbellExercise "Overhead Press" 85 15

workout = [exer1, exer2, exer3, exer4]

test = do
  mapM_ displayExercise workout

  where
    displayExercise x = putStrLn $ "Exercise: " ++ (name x) ++ " You must perform " ++ (show $ reps x) ++ "@" ++ (show $ weight x)

This compiles but dies runtime if I make the mistake of using the weight function. Understandable mistake. When lenses uses template haskell to generate instances it notices this and changes its behavior to prevent a mistake. You could remove the field accessors but in my case most of the fields were the same between datatypes. Here's how I should have written the data type once I noticed the fields did not match up:

data Exercise =
  BarbellExercise
   String -- ^ name
   Int    -- ^ reps
   Int    -- ^ weight
     |
  BodyWeightExercise
    String -- ^ name
    Int    -- reps


name :: Exercise -> String
name (BarbellExercise n _ _) = n
name (BodyWeightExercise n _) = n

reps :: Exercise -> Int
reps (BarbellExercise _ r _) = r
reps (BodyWeightExercise _ r) = r

By doing it this way, while it is a little less clean, the error are caught at compile time. By forcing me to write the functions myself I would notice any partial functions as I was writing them.

I do kind of wish ghc would have warned me. It seems like it would be really easy for it to detect such a thing.

like image 93
David McHealy Avatar answered Nov 05 '22 14:11

David McHealy