Haskell 组合透镜和棱镜的乘积之和?

Haskell 组合透镜和棱镜的乘积之和?,haskell,haskell-lens,Haskell,Haskell Lens,如果我有一个记录类型,我可以用镜头做任何我想做的事情。如果我有一个求和类型,我可以用棱镜做任何我想做的事情。但是,如果我有一个包含记录的总和,makeFields当然不会给我字段的镜头,而只给它们遍历declarePrisms似乎更有希望。根据文件, declarePrisms [d| data Exp = Lit Int | Var String | Lambda{ bound::String, body::Exp } |] 将创造 data Exp = Lit Int | Var

如果我有一个记录类型,我可以用镜头做任何我想做的事情。如果我有一个求和类型,我可以用棱镜做任何我想做的事情。但是,如果我有一个包含记录的总和,
makeFields
当然不会给我字段的镜头,而只给它们遍历
declarePrisms
似乎更有希望。根据文件,

declarePrisms [d|
  data Exp = Lit Int | Var String | Lambda{ bound::String, body::Exp }
  |]
将创造

data Exp = Lit Int | Var String | Lambda { bound::String, body::Exp }
_Lit :: Prism' Exp Int
_Var :: Prism' Exp String
_Lambda :: Prism' Exp (String, Exp)
这让我几乎达到了目的,但我真正想要的是:

data Exp = Lit Int | Var String | Lambda String Exp
data LambdaRec = { _bound::String, _body::Exp }
...
_Lambda :: Prism' Exp LambdaRec
-- bound and body lenses into LambdaRec,
-- and ideally also traversals to look at them in Exp.
class MightBeLambda t where
  type BoundOptic t
  type BodyOptic t
  bound :: BoundOptic t
  body :: BodyOptic t
instance MightBeLambda Exp where
  type BoundOptic Exp = Traversal' Exp String
  ...
instance MightBeLambda LambdaRec where
  type BoundOptic LambdaRec = Lens' LambdaRec String
有没有办法自动完成这样的工作,或者我必须手工完成


一种更疯狂的方式:

data ExpTag = LitT | VarT | LambdaT

data Exp' :: ExpTag -> * where
  Lit' :: Int -> Exp' LitT
  Var' :: String -> Exp' VarT
  Lambda' :: { _bound::String, _body::Exp } -> Exp' LambdaT

然后,可以使用
unsafeccorece
对棱镜进行邪恶的定义,以避免复制记录的任何风险。

您可以绕道另一个生成的Iso以获得此行为。(makePrisms在应用于具有单个构造函数的类型时生成Isos)

请注意,由于GHC可以进行优化,因此生成的代码中不一定使用这种中间记录类型

getBound :: Exp -> Maybe String
getBound = preview expBound

-- Generated core for getBound
--
-- getBound1 =
--   \ eta_B1 ->
--     case eta_B1 of _ {
--       __DEFAULT -> (Nothing) `cast` ...;
--       Lambda y1_a6XB y2_a6XC -> (Just y1_a6XB) `cast` ...
--     }

当然,如果您坚持对求和或变量类型使用
数据
,而不是同时使用这两种类型,即
数据Exp=Lit Int | Var String | Lambda LambdaRec
,问题就会消失。
getBound :: Exp -> Maybe String
getBound = preview expBound

-- Generated core for getBound
--
-- getBound1 =
--   \ eta_B1 ->
--     case eta_B1 of _ {
--       __DEFAULT -> (Nothing) `cast` ...;
--       Lambda y1_a6XB y2_a6XC -> (Just y1_a6XB) `cast` ...
--     }