-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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 -> b for 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