Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic lens without template haskell

I am trying to create a polymorphic lens decleration (without template haskell) for multiple types.

module Sample where
import Control.Lens
data A = A {_value:: Int}
data B = B {_value:: Int}
data C = C {_value:: String}
value = lens _value (\i x -> i{_value=x}) -- <<< ERROR

But I get following error:

Ambiguous occurrence ‘_value’
It could refer to either the field ‘_value’,
                         defined at library/Sample.hs:5:13
                      or the field ‘_value’, defined at 
library/Sample.hs:4:13
                      or the field ‘_value’, defined at 
library/Sample.hs:3:13
  |
6 | value = lens _value (\i x -> i{_value=x}) -- <<< ERROR
  |              ^^^^^^

So, goal is to have value lens to work on all three types A, B and C. Is there a way to achieve that? Thanks.

like image 691
yilmazhuseyin Avatar asked Dec 08 '22 15:12

yilmazhuseyin


2 Answers

Lenses can be derived without TH by generic-lens. You can specialize the generic field lens to a specific field and give it a name as follows.

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}

import GHC.Generics (Generic)
import Control.Lens (Lens, (^.))
import Data.Generics.Product (HasField(field))

data A = A { _value :: Int } deriving Generic
data B = B { _value :: Int } deriving Generic
data C = C { _value :: String } deriving Generic

value :: HasField "_value" s t a b => Lens s t a b
value = field @"_value"

main :: IO ()
main = print (A 0 ^. value, B 0 ^. value, C "0" ^. value)
like image 122
Li-yao Xia Avatar answered Jan 01 '23 17:01

Li-yao Xia


Haskell does not support overloading functions the way a language like Java or C++ does. To do what you want you need to use a typeclass like so.

class HasValue a b where
    value :: Lens' a b 

data A = A {_valueA:: Int}
data B = B {_valueB:: Int}
data C = C {_valueC:: String}

instance HasValue A Int where
   value = lens _valueA (\i x -> i{_valueA=x})

instance HasValue B Int where
   value = lens _valueB (\i x -> i{_valueB=x})

instance HasValue C String where
   value = lens _valueC (\i x -> i{_valueC=x}

You'll need to enable multiparameter typeclasses to do this.

like image 22
Alexander Wittmond Avatar answered Jan 01 '23 16:01

Alexander Wittmond