-module(lens). -export([ get/2, put/3, update/3, c/2, c/3, c/4, c/5, c/6, hd/0, tl/0, index/1, where/1, tuple_lens/1, pair_fst/0, pair_snd/0, triple_fst/0, triple_snd/0, triple_thd/0 , tc/0, td/0, df/2 ]). -compile({inline, [ get/2, put/3, update/3, c/2, c/3, c/4, c/5, c/6, hd/0, tl/0, list_lens/1, tuple_lens/1, pair_fst/0, pair_snd/0, triple_fst/0, triple_snd/0, triple_thd/0 ]}). % A lens is a pair of functions % { Get :: (A) -> B, Put :: (A, B) -> A }. % We can apply these in various ways to extract and replace % information, but we can also *compose* them. % See the df/2 example below, which composes 3 field selectors % to make something that can be got or put in what looks like one step. get({G,_}, X) -> G(X). put({_,P}, X, Y) -> P(X, Y). update({G,P}, X, F) -> P(X, F(G(X))). hd() -> { fun ([H|_]) -> H end , fun ([_|T], H) -> [H|T] end }. tl() -> { fun ([_|T]) -> T end , fun ([H|_], T) -> [H|T] end }. nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N-1, T). set_nth(1, [_|T], X) -> [X|T]; set_nth(N, [H|T], X) when N > 1 -> [H|set_nth(N-1, T, X)]. index(N) -> { fun (Xs) -> nth(N, Xs) end , fun (Xs, Y) -> set_nth(N, Xs, Y) end }. find(P, [H|T]) -> case P(H) of true -> H ; false -> find(P, T) end. set_find(P, [H|T], X) -> case P(H) of true -> [X|T] ; false -> [H|set_find(P, T, X) end. where(P) -> { fun (Xs) -> find(P, Xs) end , fun (Xs, Y) -> set_find(P, Xs, Y) end }. tuple_lens(N) -> { fun (T) -> element(N, T) end , fun (T, Y) -> setelement(N, T, Y) end }. pair_fst() -> { fun ({A,_}) -> A end , fun ({_,B}, A) -> {A,B} end }. pair_snd() -> { fun ({_,B}) -> B end , fun ({A,_}, B) -> {A,B} end }. triple_fst() -> { fun ({A,_,_}) -> A end , fun ({_,B,C}, A) -> {A,B,C} end }. triple_snd() -> { fun ({_,B,_}) -> B end , fun ({A,_,C}, B) -> {A,B,C} end }. triple_thd() -> { fun ({_,_,C}) -> C end , fun ({A,B,_}, C) -> {A,B,C} end }. c({G1,P1}, {G2,P2}) -> { fun (X) -> G2(G1(X)) end , fun (X, Y) -> P1(X, P2(G1(X), Y)) end }. c({G1,P1}, {G2,P2}, {G3,P3}) -> { fun (F0) -> G3(G2(G1(F0))) end , fun (F0, U3) -> F1 = G1(F0), F2 = G2(F1), U2 = P3(F2, U3), U1 = P2(F1, U2), P1(F0, U1) end }. c({G1,P1}, {G2,P2}, {G3,P3}, {G4,P4}) -> { fun (F0) -> G4(G3(G2(G1(F0)))) end , fun (F0, U4) -> F1 = G1(F0), F2 = G2(F1), F3 = G3(F2), U3 = P4(F3, U4), U2 = P3(F2, U3), U1 = P2(F1, U2), P1(F0, U1) end }. c({G1,P1}, {G2,P2}, {G3,P3}, {G4,P4}, {G5,P5}) -> { fun (F0) -> G5(G4(G3(G2(G1(F0))))) end , fun (F0, U5) -> F1 = G1(F0), F2 = G2(F1), F3 = G3(F2), F4 = G4(F3), U4 = P5(F4, U5), U3 = P4(F3, U4), U2 = P3(F2, U3), U1 = P2(F1, U2), P1(F0, U1) end }. c({G1,P1}, {G2,P2}, {G3,P3}, {G4,P4}, {G5,P5}, {G6,P6}) -> { fun (F0) -> G6(G5(G4(G3(G2(G1(F0)))))) end , fun (F0, U6) -> F1 = G1(F0), F2 = G2(F1), F3 = G3(F2), F4 = G4(F3), F5 = G5(F4), U5 = P6(F5, U6), U4 = P5(F4, U5), U3 = P4(F3, U4), U2 = P3(F2, U3), U1 = P2(F1, U2), P1(F0, U1) end }. %----------------------------------------------------------------------- % Record fields #rec.field are integers but lenses would be nicer. %----------------------------------------------------------------------- -record(a, {p,q,r,s}). df(R, Y) -> % R.p.q.r <- Y put(c(tuple_lens(#a.p), tuple_lens(#a.q), tuple_lens(#a.r)), R, Y). %----------------------------------------------------------------------- % Examples. %----------------------------------------------------------------------- tc() -> c(pair_fst(), triple_thd()). td() -> {{1,2,3},4}. % 1> c(lens). % {ok,lens} % 2> lens:df({a,{a,p2,{a,p3,q3,r3,s3},r2,s2},q1,r1,s1}, 42). % {a,{a,p2,{a,p3,q3,42,s3},r2,s2},q1,r1,s1} % 3> lens:get(lens:tc(), lens:td()). % 3 % 4> lens:put(lens:tc(), lens:td(), 5). % {{1,2,5},4} % 5> lens:update(lens:tc(), lens:td(), fun (N) -> 10*N-1 end). % {{1,2,29},4} %----------------------------------------------------------------------- % Why we want cross-module inlining. %----------------------------------------------------------------------- % consider % lens:update(lens:triple_snd(), Triple, fun (X) -> X+1 end). % (1) Expand lens:triple_snd/0 inline % lens:update({G,P}, Triple, fun (X) -> X + 1 end) % where G = fun ({_,B,_}) -> B end % and P = fun ({A,_,C}, B) -> {A,B,C} end % (2) Expand lens:update/3 inline % P(X, F(G(X)) % where X = Triple % and F = fun (X) -> X + 1 end % and G, P are unchanged % (3) Expand G(X) inline % {_,B,_} = Triple, P(X, F(B)) % (4) expand F(X) inline % {_,B,_} = Triple, B1 = B + 1, P(Triple, B1) % (5) expand P inline % {_,B,_} = Triple, B1 = B + 1, {A1,_,C1} = Triple, {A1,B1,C1} % (6) Fuse matches of Triple % {A1,B,C1} = Triple, B1 = B + 1, {A1,B1,C1}. % This is about as good as it gets. The only improvement % remaining would be to push the original decomposition back % into the function head, which is OK for Prolog but not Erlang. %----------------------------------------------------------------------- % Lists. %----------------------------------------------------------------------- % % We can index into a matrix represented as a list of lists: % index2(Row, Col) -> c(list_lens(Row), list_lens(Col)). % a(i,j) := x => put(index2(I,J), A, X). % Easy to do, but the high cost of indexing into lists means % this will never be a good idea. % Issue: update(...) will in general traverse its subject % twice. Maybe we should include 'update' as a third function.