用于产品总和的组合镜头和棱镜?

时间:2016-01-11 22:50:52

标签: haskell lens

如果我有记录类型,我可以用镜头做任何我想做的事情。如果我有一个总和类型,我可以用棱镜做任何我想做的事情。但是,如果我有一个包含记录的总和,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

然后可以使用unsafeCoerce来定义棱镜,以避免复制记录的任何风险。

1 个答案:

答案 0 :(得分:1)

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

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" />
<input type="checkbox" />

请注意,由于GHC可以执行此优化,因此不必在生成的代码中使用此中间记录类型。

{-# LANGUAGE TemplateHaskell #-}

module Demo where

import Control.Lens

data Exp = Lit Int | Var String | Lambda String Exp
data LambdaRec = LambdaRec { _bound::String, _body::Exp }

makePrisms ''Exp
makePrisms ''LambdaRec
makeLenses ''LambdaRec

_ExpLambdaRec :: Prism' Exp LambdaRec
_ExpLambdaRec = _Lambda . from _LambdaRec

-- Example using _ExpLambdaRec
expBound :: Traversal' Exp String
expBound = _ExpLambdaRec . bound