Различные типы сеттеров и геттеров в линзах Haskell
У меня есть data type
G
, которые получили поле _repr :: Data.Graph.Inductive.Gr String String
, Обычный способ, при добавлении нового узла в Gr
график, мы должны предоставить LNode a
объект, который в основном определяется как кортеж (Int, a)
где Int - индекс узлов в Graph - см. пример функции add
ниже.
Я хочу реализовать функцию addx
, который будет автоматически вычислять индекс (например, используя Data.Graph.Inductive.newNodes
функция). Я хочу addx
иметь подпись addx :: String -> G -> Int
и эта функция будет вычислять новый свободный индекс, изменять график G и возвращать этот вычисленный индекс. Возможно ли в Haskell создать такую функцию (которая изменит существующий объект - G
в данном случае) - используя линзы или что-то в этом роде?
Я видел, что объектив Хаскелла определяется как lens :: (a -> c) -> (a -> d -> b) -> Lens a b c d
а линза - это в основном "геттер" и "сеттер", поэтому ее сигнатура допускает различные типы выходных данных геттера (c
), установочное значение (d
) и установочный выход (b
).
import qualified Data.Graph.Inductive as DG
data G = G { _repr :: DG.Gr String String, _name::String} deriving ( Show )
empty :: G
empty = G DG.empty ""
add :: DG.LNode String -> G -> G
add node g = g{_repr = DG.insNode node $ _repr g}
-- is it possible to define it?
addx :: String -> G -> Int
addx name g = undefined
main :: IO ()
main = do
let g = add (1, "test2")
$ add (0, "test1")
$ empty
n1 = addx "test2" g
g2 = DG.insEdge(n1,0)
$ DG.insEdge(0,1)
print $ g
1 ответ
Ваш тип для addx
сломан, так как вы не можете изменить G
в чистой функции без возврата измененной формы, такой как addx1 :: String -> G -> (Int, G)
, Если у вас есть умный взгляд на монады Хаскелла, вы можете заметить, что он имеет изоморфный тип, addx2 :: String -> State G Int
,
Мы можем все выровнять к этой "государственной" ориентации
add' node = do g <- get
put $ g { _repr = DB.insNode node $ _repr g }
и сделать его более кратким с линзами
add'' node = repr %= DB.insNode node
Настоящая проблема здесь заключается в том, чтобы, в конце концов, отследить идентичность узла. Одним из способов было бы нести его вместе с repr
в вашем типе
data G = G { _repr :: DG.Gr String String, _name :: String, _index :: Int }
empty = G DG.empty "" 0
затем используйте это при построении узлов (снова используя линзы!)
addx' name = do i <- use index
repr %= DB.insNode (i, node)
i += 1