Initial sketch of a free applicative implementation - #9
Conversation
|
Wow 😄 |
|
:) I'd still like to work on #8 but might not have a chance until this weekend. It's on the list to do though! |
|
@ethul Here you go. Sorry the names are a bit crap, I did this using typed holes for the most part. I think it's right though, it should correspond to the version which uses instance extendFreeAp :: Extend f => Extend (FreeAp f) where
extend f x@(Pure a) = Pure (f x)
extend f (Ap x) = Ap (runExists (\(ApF h j) ->
mkExists (ApF h (\u -> extend (\g a1 -> f (map (_ $ a1) g)) (j u)))) x)
instance comonadFreeAp :: Comonad f => Comonad (FreeAp f) where
extract (Pure a) = a
extract (Ap x) = runExists (\(ApF h j) ->
let a1 = extract (h unit)
a2 = extract (j unit)
in a2 a1) x |
|
Oh, I did it on the wrong branch 😆 Oh well, I can try it on the new branch at some point, unless you beat me to it. |
|
Thanks for giving the instances a go! I will probably take a look this weekend, but I am open to any suggestions on the implementations you may have. Thanks! |
|
The only suggestion I can give is to think in terms of In fact, you might like to consider the representation in terms of |
|
Thanks for the insight. I will definitely dig more into this. I am interested by a representation in terms of |
|
btw is there any resource to understand the "day convolution" which does not require too much category theory? (can't understand ncatlab) |
|
Phil wrote a bit about them a while back: http://blog.functorial.com/posts/2016-08-08-Comonad-And-Day-Convolution.html but since they are very much a category theory based construct there's not going to be a whole lot of other material out there, I suspect! |
|
@garyb I know that article, but didn't quite understood it, thanks tho! |
|
I've added an implementation based on Another question is concerning thunks. @garyb you asked about this earlier. The current v2.0.0 implementation thunks the |
|
@ethul I have made 1 to 1 port of https://www.eyrie.org/~zednenem/2013/05/27/freeapp to js it's pritty fast, but if the tree is large (for example 4000) stack overflows (on 3000 it works fine) const buildTree = n => {
let res = FreeAp.of(function f(a) {
return a == n - 1 ? a : f
})
for (let i = 0; i < n; i++) {
res = FreeAp.lift(i).ap(res)
}
return res
}
buildTree(4000).foldPar(Identity.of, Identity).toString()Can you try run benchmark with largar input (more than 2000) as i think it would overflow. |
|
@safareli You're right. I have tried this before and it does overflow. Certainly something I'd like to look into further. Thanks for bringing this up! |
|
if we have large chain of function composition ( hypothetical solution would be to have a heterogenous list of functions, which could be instance of Category and it's composition would just build up the List (or just make it a Functor and data FunList a b where
One :: (a -> b) -> FunList a b
Cons :: (z -> b) -> FunList a z -> FunList a b
instance Functor (FunList z) where
fmap :: (a -> b) -> FunList z a -> FunList z b
fmap g f = Cons g f
-- or just `fmap = Cons`Then we could have some function to actually run :: FunList a b -> a -> bfor example in js: // list of functions
const fs = Array(100000).fill().map((_,idx) => x => x + idx)
const compose = (f,g) => x => f(g(x))
// if we compose them and then run it
fs.reduce(compose)(1) // we get stack overflow
// but if this is stack safe
fs.reduceRight((arg,f) => f(arg),1)so if we have wrapper for function which during composition builds up a list then it could be executed without stack issues. i think we could add more variants to the FunList type and make it a monad if needed |
|
@safareli I like your idea. Thanks for the example. I will have to dig into this a bit more this weekend, but I like the direction of this so far. |
|
I bet you could write |
|
Good call!
…On Fri, Dec 9, 2016 at 13:10 Phil Freeman ***@***.***> wrote:
I bet you could write retractFreeAp as a tail recursive function pretty
easily using the Day representation, although I haven't tried it yet.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAVYy_bmnQOn5QkEl77M7mu2wOIgUg3fks5rGZmngaJpZM4LA1bj>
.
|
|
btw just wrote about the stack safe function composition |
|
Nice! Thanks for the link
…On Fri, Dec 9, 2016 at 19:22 Irakli Safareli ***@***.***> wrote:
btw just wrote about the stack safe function composition
***@***.***/stack-safe-function-composition-85d61feee37e#.876z5dsdg>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAVYy8-AOEd0OATBGSwHHrT3EJo9P-MMks5rGfC3gaJpZM4LA1bj>
.
|
|
continuation of that first article https://www.eyrie.org/~zednenem/2013/06/freeapp-2 |
|
Nice! Thanks for the link
…On Sat, Dec 17, 2016 at 15:53 Irakli Safareli ***@***.***> wrote:
continuation of that first article
https://www.eyrie.org/~zednenem/2013/06/freeapp-2
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAVYyxp80ULuKtgWdWwHo5tciOJDQlBOks5rJEu9gaJpZM4LA1bj>
.
|
|
Hey recently i was thinking on how to make it stack safe and and came up with this implementation of FreeAp which has //data Par f a where
// Pure :: a -> Par f a
// Lift :: f a -> Par f a
// Ap :: Par f (a -> b) -> Par f a -> Par f b
const Par = union('Par', {
Pure: ['x'],
Lift: ['i'],
Ap: ['i', 'x'],
})
const { Pure, Lift, Ap } = Par
Object.assign(Par, {
// :: a -> Par f a
of: Pure,
// :: f a -> Par f a
lift: Lift,
})
const foldArg = (node, f, T) => {
if (node.tag == 'Pure') {
return of(T, node.x)
} else if (node.tag == 'Lift') {
return f(node.f)
}
throw new TypeError('Invalid argument for foldArg')
}
Object.assign(Par.prototype, {
// :: Par f a ~> (a -> b) -> Par f b
map(f) {
return ap(Par.of(f), this)
},
// :: Par f a ~> Par f (a -> b) -> Par f b
ap(that) {
return Ap(that, this)
},
// :: Applicative g => Par f a ~> (Ɐ x. f x -> g x, TypeRep g) -> g a
foldPar(f, T) {
// # this is non stack safe version:
// return this.cata({
// Pure: (x) => of(T, x),
// Lift: (i) => f(i),
// Ap: (i, x) => ap(i.foldPar(f,T), x.foldPar(f,T)),
// })
// # this is stack safe version:
const args = [this]
const fns = []
while (true) {
let arg = args.pop()
if(arg.tag === 'Ap') {
while (arg.tag == 'Ap') {
args.push(arg.x)
arg = arg.f
}
fns.push(foldArg(arg, f, T))
} else {
const resArg = foldArg(arg, f, T)
if (fns.length == 0) {
return resArg
}
let resFn = ap(fns.pop(), resArg)
if (args.length > 0) {
fns.push(resFn)
} else if (fns.length > 0) {
fns.push(resFn)
return fns.reduce((fn, arg) => ap(fn, arg))
} else {
return resFn
}
}
}
},
// :: Par f a ~> (Ɐ x. f x -> g x) -> Par g a
hoistPar(f) {
return this.foldPar(compose(Par.lift)(f), Par)
},
// :: (Applicative f) => Par f a ~> TypeRep f -> f a
retractPar(m) {
return this.foldPar(id, m)
},
// :: Par f a ~> (Ɐ x. f x -> Par g x) -> Par g a
graftPar(f) {
return this.foldPar(f, Par)
},
})I used this snippet to test stack safety: const apTimes = (n) => {
const args = []
const f = (a) => {
args.push(a)
if (args.length == n) {
return args.length
}
return f
}
let res = Par.lift(f)
for (let i = 0; i < n; i ++ ) {
res = ap(res, Par.lift(i))
}
return res
}
const testN = MAX_STACK
t.equals(apTimes(testN).hoistPar(id).foldPar(Identity.of, Identity).get(), testN, 'is Par stack safe')
t.equals(apTimes(testN).foldPar(Identity.of, Identity).get(), testN, 'Works with Identity')Will soon update the PR safareli/free#31 I was working on. |
|
Nice! Thanks for the code snippet. I will have to dig into this PR again.
…On Sun, Jan 15, 2017 at 14:55 Irakli Safareli ***@***.***> wrote:
Hey recently i made a quick implementation of FreeAp which has O(1)
complexity on map and ap and O(n) on fold which is also stack safe if
target applicative is stacksafe:
//data Par f a where
// Pure :: a -> Par f a
// Lift :: f a -> Par f a
// Ap :: Par f (a -> b) -> Par f a -> Par f b
const Par = union('Par', {
Pure: ['x'],
Lift: ['i'],
Ap: ['i', 'x'],
})
const { Pure, Lift, Ap } = Par
Object.assign(Par, patch({
// :: a -> Par f a
of: Pure,
// :: f a -> Par f a
lift: Lift,
}))
const foldArg = (node, f, T) => {
if (node.tag == 'Pure') {
return of(T, node.x)
} else if (node.tag == 'Lift') {
return f(node.f)
}
throw new TypeError('Invalid argument for foldArg')
}
Object.assign(Par.prototype, {
// :: Par f a ~> (a -> b) -> Par f b
map(f) {
return ap(Par.of(f), this)
},
// :: Par f a ~> Par f (a -> b) -> Par f b
ap(that) {
return Ap(that, this)
},
// :: Applicative g => Par f a ~> (Ɐ x. f x -> g x, TypeRep g) -> g a
foldPar(f, T) {
// # this is non stack safe version:
// return this.cata({
// Pure: (x) => of(T, x),
// Lift: (i) => f(i),
// Ap: (i, x) => ap(i.foldPar(f,T), x.foldPar(f,T)),
// })
// # this is stack safe version:
const args = [this]
const fns = []
while (true) {
let arg = args.pop()
if(arg.tag === 'Ap') {
while (arg.tag == 'Ap') {
args.push(arg.x)
arg = arg.f
}
fns.push(foldArg(arg, f, T))
} else {
const resArg = foldArg(arg, f, T)
if (fns.length == 0) {
return resArg
}
let resFn = ap(fns.pop(), resArg)
if (args.length > 0) {
fns.push(resFn)
} else if (fns.length > 0) {
fns.push(resFn)
return fns.reduce((fn, arg) => ap(fn, arg))
} else {
return resFn
}
}
}
},
// :: Par f a ~> (Ɐ x. f x -> g x) -> Par g a
hoistPar(f) {
return this.foldPar(compose(Par.lift)(f), Par)
},
// :: (Applicative f) => Par f a ~> TypeRep f -> f a
retractPar(m) {
return this.foldPar(id, m)
},
// :: Par f a ~> (Ɐ x. f x -> Par g x) -> Par g a
graftPar(f) {
return this.foldPar(f, Par)
},
})
I used this snippet to test stack safety:
const apTimes = (n) => {
const args = []
const f = (a) => {
args.push(a)
if (args.length == n) {
return args.length
}
return f
}
let res = Par.lift(f)
for (let i = 0; i < n; i ++
) {
res = ap(res, Par.lift(i))
}
return res
}
const testN = MAX_STACK
t.equals(apTimes(testN).hoistPar(id).foldPar(Identity.of, Identity).get(), testN, 'is Par stack safe')
t.equals(apTimes(testN).foldPar(Identity.of, Identity).get(), testN, 'Works with Identity')
Will soon update the @safereli/Free PR I was working.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#9 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAVYy3EfcLnXywJP6vHzLI9p-zBGF6diks5rSnnAgaJpZM4LA1bj>
.
|
|
The implementation of fold, I had written in last comment, was incorrect and this one is correct (it needs a bit of refactoring tho). |
|
Superseded by #13 |




The goal of this implementation is to improve performance
characteristics of the operations of the free applicative functor in
PureScript.
Reference #7
Reference #8