nested - How to implement “efficient generalized fold” in F#? -


in paper of martin et al. read efficient generalized folds nestet data types. paper talks haskell , want try in f#.

so far managed follow nest example including implementation of gfold.

type pair<'a> = 'a * 'a type nest<'a> = nil | cons of 'a * nest<pair<'a>>  let example =     cons(1,         cons((2, 3),             cons(((4, 5), (6, 7)),                 nil             )         )     )  let pair (f:'a -> 'b) ((a, b):pair<'a>) : pair<'b> = f a, f b  let rec nest<'a, 'r> (f:'a -> 'r) : nest<'a> -> nest<'r> = function     | nil -> nil     | cons(x, xs) -> cons(f x, nest (pair f) xs)  //val gfold : e:'r -> f:('a * 'r -> 'r) -> g:(pair<'a> -> 'a) -> _arg1:nest<'a> -> 'r let rec gfold e f g : nest<'a> -> 'r = function     | nil -> e     | cons(x, xs) ->         f(x, gfold e f g (nest g xs))  let uncurry f (a, b) = f b  let = uncurry (+)  let sum = example |> gfold 0 up 

unfortunately, gfold seems have quadratic complexity , that's why authors came efold. can guess, that's 1 couldn't working. after fiddling many type annotations, came version has tiny squiggle left:

let rec efold<'a, 'b, 'r> (e:'r) (f:'a * 'r -> 'r) (g:(pair<'a> -> pair<'a>) -> 'a -> 'a) (h:_) (nest:nest<'a>) : 'r =     match nest     | nil -> e     | cons(x, xs) -> f(h x, efold e f g ((g << pair) h) xs)                                                         ^^ 

the remaining unspecified type 1 of h. compiler infers val h : ('a -> 'a) think there need different types.

the error message provided reads

error type mismatch. expecting a
nest<'a>
given a
nest<pair<'a>>
resulting type infinite when unifying ''a' , 'pair<'a>'

with correct type of h error should vanish. don't understand enough haskell translate f#.

see this discussion possible typo in paper.


update: understand kvb's answer:

so h transforms input type intermediate type, in regular fold accumulator may of different type. g used reduce 2 intermediate typed values 1 while f gets intermediate type , input type produce output typed values. of course e of output type.

h indeed directly applied values encountered during recursion. g on other hand used make h applicable progressively deeper types.

just looking @ first f examples, doesn't seem work apart applying h , fuelling recursion. in sophisticated approach can see important 1 wrt. comes out, i.e. it's work horse.

is right?

the correct definition of efold in haskell like:

efold :: forall n m b.     (forall a. n a)->     (forall a.(m a, n (pair a)) -> n a)->     (forall a.pair (m a) -> m (pair a))->     (forall a.(a -> m b) -> nest -> n b)  efold e f g h nil = e  efold e f g h (cons (x,xs)) = f (h x, efold e f g (g . pair h) xs 

this can't translated f# in full generality because n , m "higher-kinded types" - type constructors create type when given argument - aren't supported in f# (and have no clean representation in .net).

interpretation

your update asks how interpret arguments fold. perhaps easiest way see how fold works expand out happens when apply fold example. this:

efold e f g h example ≡     f (h 1, f ((g << pair h) (2, 3), f ((g << pair (g << pair h)) ((4,5), (6,7)), e))) 

so h maps values type can serve f 's first agument. g used apply h more nested pairs (so can go using h function of type a -> m b pair -> m (pair b) pair (pair a) -> m (pair (pair b)) etc.), , f repeatedly applied spine combine results of h results of nested calls f. finally, e used once, serve seed of nested call f.

i think explanation agrees you've deduced. f critical combining results of different layers. g matters, too, since tells how combine pieces within layer (e.g. when summing nodes, needs sum left , right nested sums; if wanted use fold build new nest values @ each level reversed of input, use g looks fun (a,b) -> b,a).

simple approach

one option create specialized implementations of efold each n, m pair care about. example, if want sum lengths of lists contained in nest n _ , m _ both int. can generalize slightly, case n _ , m _ don't depend on arguments:

let rec efold<'n,'m,'a> (e:'n) (f:'m*'n->'n) (g:pair<'m> -> 'm) (h:'a->'m) : nest<'a> -> 'n = function | nil -> e | cons(x,xs) -> f (h x, efold e f g (g << (pair h)) xs)  let total = efold 0 up id example 

on other hand, if n , m use arguments, you'd need define separate specialization (plus, may need create new types each polymorphic argument, since f#'s encoding of higher-rank types awkward). instance, collect nest's values list want n 'a = list<'a> , m 'b = 'b. instead of defining new types argument type of e can observe value of type forall 'a.list<'a> [], can write:

type listidf =     abstract apply : 'a * list<pair<'a>> -> list<'a>  type listidg =     abstract apply : pair<'a> -> pair<'a>  let rec efold<'a,'b> (f:listidf) (g:listidg) (h:'a -> 'b) : nest<'a> -> list<'b> = function | nil -> [] | cons(x,xs) -> f.apply(h x, efold f g (pair h >> g.apply) xs)  let tolist n = efold { new listidf member __.apply(a,l) = a::(list.collect (fun (x,y) -> [x;y]) l) } { new listidg member __.apply(p) = p } id n 

sophisticated approach

while f# doesn't directly support higher-kinded types, turns out it's possible simulate them in faithful way. approach taken higher library. here's minimal version of like.

we create type app<'t,'a> represent type application t<'a>, we'll create dummy companion type can serve first type argument app<_,_>:

type app<'f, 't>(token : 'f, value : obj) =              if obj.referenceequals(token, unchecked.defaultof<'f>)             raise <| new system.invalidoperationexception("invalid token")      // apply secret token have access encapsulated value     member self.apply(token' : 'f) : obj =         if not (obj.referenceequals(token, token'))             raise <| new system.invalidoperationexception("invalid token")         value  

now can define companion types type constructors care (and these can live in shared library):

// app<const<'a>, 'b> represents value of type 'a (that is, ignores 'b) type const<'a> private () =     static let token = const ()     static member inj (value : 'a) =         app<const<'a>, 'b>(token, value)     static member prj (app : app<const<'a>, 'b>) : 'a =         app.apply(token) :?> _  // app<list, 'a> represents list<'a> type list private () =      static let token = list()     static member inj (value : 'a list) =         app<list, 'a>(token, value)     static member prj (app : app<list, 'a>) : 'a list =         app.apply(token) :?> _  // app<id, 'a> represents plain 'a type id private () =     static let token = id()     static member inj (value : 'a) =         app<id, 'a>(token, value)     static member prj (app : app<id, 'a>) : 'a =         app.apply(token) :?> _  // app<nest, 'a> represents nest<'a> type nest private () =     static let token = nest()     static member inj (value : nest<'a>) =         app<nest, 'a>(token, value)     static member prj (app : app<nest, 'a>) : nest<'a> =         app.apply(token) :?> _ 

now can define higher-rank types arguments of efficient fold once , all:

// forall a. n type e<'n> =     abstract apply<'a> : unit -> app<'n,'a>  // forall a.(m a, n (pair a)) -> n a) type f<'m,'n> =     abstract apply<'a> : app<'m,'a> * app<'n,'a*'a> -> app<'n,'a>  // forall a.pair (m a) -> m (pair a)) type g<'m> =     abstract apply<'a> : app<'m,'a> * app<'m,'a> -> app<'m,'a*'a> 

so fold just:

let rec efold<'n,'m,'a,'b> (e:e<'n>) (f:f<'m,'n>) (g:g<'m>) (h:'a -> app<'m,'b>) : nest<'a> -> app<'n,'b> = function | nil -> e.apply() | cons(x,xs) -> f.apply(h x, efold e f g (g.apply << pair h) xs) 

now call efold need sprinkle in calls various inj , prj methods, otherwise looks we'd expect:

let tolist n =      efold { new e<_> member __.apply() = list.inj [] }            { new f<_,_> member __.apply(m,n) = id.prj m :: (n |> list.prj |> list.collect (fun (x,y) -> [x;y])) |> list.inj }           { new g<_> member __.apply(m1,m2) = (id.prj m1, id.prj m2) |> id.inj }           id.inj           n     |> list.prj  let sumelements n =     efold { new e<_> member __.apply() = const.inj 0 }           { new f<_,_> member __.apply(m,n) = const.prj m + const.prj n |> const.inj }           { new g<_> member __.apply(m1,m2) = const.prj m1 + const.prj m2 |> const.inj }           const.inj           n     |> const.prj  let reverse n =      efold { new e<_> member __.apply() = nest.inj nil }           { new f<_,_> member __.apply(m,n) = cons(id.prj m, nest.prj n) |> nest.inj }           { new g<_> member __.apply(m1,m2) = (id.prj 2, id.prj m1) |> id.inj }           id.inj           n     |> nest.prj 

hopefully pattern here clear: in each object expression, application method projects out each argument, operates on them, , injects result app<_,_> type. inline magic, can make more consistent (at cost of few type annotations):

let inline (|prj|) (app:app< ^t, 'a>) = (^t : (static member prj : app< ^t, 'a> -> 'b) app) let inline prj (prj x) = x let inline inj x = (^t : (static member inj : 'b -> app< ^t, 'a>) x)  let tolist n =      efold { new e<list> member __.apply() = inj [] }            { new f<id,_> member __.apply(prj m, prj n) = m :: (n |> list.collect (fun (x,y) -> [x;y])) |> inj }           { new g<_> member __.apply(prj m1,prj m2) = (m1, m2) |> inj }           inj           n     |> prj  let sumelements n =     efold { new e<const<_>> member __.apply() = inj 0 }           { new f<const<_>,_> member __.apply(prj m, prj n) = m + n |> inj }           { new g<_> member __.apply(prj m1,prj m2) = m1 + m2 |> inj }           inj           n     |> prj  let reverse n =      efold { new e<_> member __.apply() = nest.inj nil }           { new f<id,_> member __.apply(prj m,prj n) = cons(m, n) |> inj }           { new g<_> member __.apply(prj m1,prj m2) = (m2, m1) |> inj }           inj           n     |> prj 

Comments

Popular posts from this blog

account - Script error login visual studio DefaultLogin_PCore.js -

xcode - CocoaPod Storyboard error: -