xyncro / freya

Freya Web Stack - Meta-Package
https://freya.io
Other
329 stars 30 forks source link

[Suggestion] Combinator operators for partial application of functions wrapped in Freya computations #189

Open Vidarls opened 8 years ago

Vidarls commented 8 years ago

As I previously mentioned in #182 I've had some issues getting to grips being inside / outside Freya computations. Particularly with regards to passing around partially applied functions that requires arguments from inside a Freya computation, (and thus themselves get wrapped in a Freya computation after..)

I ended up creating the some custom operators (again).

(Btw, I really wish F# would support named operators, rather than just symbolic ones, would help readability a lot..)

I do have a lot of holes in my functional vocabulary, so I have no idea what to actually call these but here they are:

let (<!^>) f v = 
    freya {
        let! f = f 
        return f v
    } 

let (<^!>) f v = 
    freya {
        let! v = v
        return! f v
    }

The first operator will take a function wrapped in a Freya computation, "dereference" it(?) and apply the given argument, returning the result wrapped in a Freya computation.

Example:

open Freya.Core.Operators

let myFunction with' some args = 
    (...)

let myValueInAFreya = 
    freya {
        return "some stuff"
    }

let myValueNotInAFreya = 
    "some stuff"
let myPartiallyAppliedOne = myFunction <!> myValueInAFreya
// Above is now Freya<'a->'b->'c>
// I want to add another arg partially applied

let myPartiallyAppliedTwo = myPartiallyAppliedOne <!^> myValueNotInAFreya
// Above is now Freya<'a->'b>

It is possible I have misunderstood something, but adding the new operator helped me quite a bit..

The scenario for the other operator is a bit more weird. I ended up creating it because using the standard <!> map(?) operator ended up giving med nested Freyas (Freya<Freya<'t>>) which I was not what I was looking for.

Example:

let myFunctionNotInAFreya some arg = 
    (...)

let myFunctionReturningAFreyaWrappedFun () = 
    freya {
        return (fun x -> x.ToString())
    }

//this gives me nested Freyas:

let normalMap = myFunctionNotInAFreya <!> (myFunctionReturningAFreyaWrappedFun ())
// above gives `Freya<Freya<'a->'b>>`

// using my operator:
let asExpected = myFunctionNotInAFreya <^!> (myFunctionReturningAFreyaWrappedFun ())
//above gives `Freya<'a->'b>`as expected.

This may or may not be something to include as part of the core operators..

Vidarls commented 8 years ago

ping @kolektiv This is the one I would like some feedback on before doing a PR on operators.

kolektiv commented 8 years ago

I don't see why not :) There are ways of writing them using the more "common" operators, but they're less concise, and we wouldn't be mandating these. I think this would be fine, but I'd probably add them to an extra module under Freya.Operators - something like Freya.Operators.Extended to signify that these are probably a bit more special purpose. But I'd be happy to take that I think!

Vidarls commented 8 years ago

Ok, I'll give it a go.

Just for my education: would you mind giving an example of how to to this with the "common" operators. And perhaps also give some suggestion on what to name my shortcuts?

kolektiv commented 8 years ago

Sure, let me put something together :) I'll have a think on the naming!

kolektiv commented 8 years ago

Not sure how clear this is, but maybe useful - and I think doing the same thing! The first one is slightly awkward, although could be slightly tidied up by defining a function let apply a b = b a and then replacing the function definition with simply apply x as shown:

// First

let (<!^>) f v =
    freya {
        let! f = f
        return f v }

let double =
    freya {
        return (fun x -> x * 2) }

let doubled x =
    double <!^> x

// or

let doubled' x =
    (fun f -> f x) <!> double

// or

let apply a b =
    b a

let doubled'' x =
    apply x <!> double

// Second

let (<^!>) f v =
    freya {
        let! v = v
        return! f v }

let triple x =
    freya {
        return x * 3 }

let tripled x =
    triple <^!> Freya.init x

// or

let tripled' x =
    Freya.init x >>= triple // or: triple =<< Freya.init x

Hopefully that's useful?