-
-
Notifications
You must be signed in to change notification settings - Fork 24
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
Ideas about env
.
#74
Comments
Very interesting! Here are some comments and questions:
|
That's clearly the nicer way! No surprises, and it takes very little effort to adjust it explicitly: $.env.loosy().concat(myEnv.loosy())
Whoops. Fixed :D
Nah, they're just the names of the sanctuary packages to load, without the
Me neither. But it's an idea I thought worth sharing. You bring up some good points against its employment. The nice thing is that the API for the end-user wouldn't have to change if we do decide to add bilby polymorphism at some point. |
I'd prefer this approach. If we're to support "plug-ins" I'd like to provide a level playing field rather than privilege
Absolutely! Thank you for doing so. I'll give myself some time to mull it over. |
I really love this thread! I thought I'd chime in with a couple of thoughts as well. API ThoughtsSorry about the big header above. I just feel bad if I break spec and don't start with an h1. I think there are 2 sensible approaches to think about this. "Doing one thing well" and how people will use the API. One thing wellSanctuary is the culmination of 4 things:
Use casesOn use cases, I think there are 3 that need to be considered. a) A user wants Sanctuary to reason with more types in their application I think we've already thought about some of the issues with (a), but I at least haven't thought as much about the (b) & (c) cases. Also, modules built with (b) & (c) will have a-c for themselves. Similarly, the ability to disable typechecking will need to be dealt with by modules consuming Waiting for defMy favourite API to date has been functions waiting for This API gives a natural decomposition of a project using IssuesThe first is that there's a bit of a weird dependence on which types make its way into the module of needed types. The tail is wagging the dog. I think if there're not too many barriers to making many small type modules and combining them, that's not a big issue. The second is that this is a relatively large amount of overhead for a small typed module. I don't think there's anything wrong with having to extract out the types (though they could just be exported on the library). Perhaps a bigger concern is that it may lead to people exporting their modules with 'types sealed in' (like in Sanctuary currently). I think we should make it as easy as possible to let people do 'the right thing'. Function-level responsibility@Avaq's suggestions have a lot to recommend them (and I'm fond of waiting for Functions created by
|
Great comment. I've read it twice but feel I need to sketch the different options in order to get a better sense of how they would affect users.
I loved this when I first read it, but I spotted a problem upon second reading: |
Great catch! I guess there are 2 responses. The first is a user providing a function that throws away the first 3 arguments and curries the implementation is still fairly low overhead and lets type checking be turned off. The other is that we could go Purescript-FFI and define the functions as manually curried. I'm not sure what would go in to having the type checking percolate recursively to the resulting functions though |
Currently one could provide
Wouldn't this mean that if the user-provided function were to return the implementation untouched, It seems that what would be ideal is a function equivalent to sanctuary-def's internal Currently type Options = { checkTypes :: Boolean, env :: Array Type }
$.def :: String -> StrMap (Array TypeClass) -> Array Type -> AnyFunction -> Options -> AnyFunction Before: const def = $.create({checkTypes: …, env: …});
// add :: Number -> Number -> Number
const add =
def('add',
{},
[$.Number, $.Number, $.Number],
(x, y) => x + y); After: // add_ :: Options -> Number -> Number -> Number
const add_ =
$.def('add',
{},
[$.Number, $.Number, $.Number],
(x, y) => x + y);
// add :: Number -> Number -> Number
const add = add_({checkTypes: …, env: …}); How would this affect Sanctuary? The API needn't change at all. We could continue to provide We could do away with const S = require('sanctuary');
// env :: Array Type
const env = …;
// opts :: { checkTypes :: Boolean, env :: Array Type }
const opts = {checkTypes: …, env: …}
S.add_(opts, 2, 2); // => 4
S.withOptions({checkTypes: true, env: env}, S => {
S.add(2, 2); // => 4
S.add('foo', 'bar'); // ! TypeError
// ...
});
S.withOptions({checkTypes: false, env: env}, S => {
S.add(2, 2); // => 4
S.add('foo', 'bar'); // => 'foobar'
// ...
});
// We could even support this by having default options (as we currently do).
S.add(2, 2); // => 4 I'm not sure this is any better than our current solution ( I don't know whether I've proposed something useful or whether I've walked in a complete circle. What exactly is the problem we'd like to solve? |
I think for me the answer is still to take I don't have much time now, but I'll try and justify that this weekend |
Sorry for the delay! My reasons for accepting I agree that it's a good idea not to change Sanctuary's API. I do think it's a good idea to extend it, because I don't feel very comfortable using |
I'd very much appreciate concrete examples to discuss. I'd like to see how advanced Sanctuary usage would change (I believe we're all in favour of Basic usage, before: const S = require('sanctuary'); Basic usage, after: const S = require('sanctuary'); Advanced usage, before: const envvar = require('envvar');
const sanctuary = require('sanctuary');
const Bar = require('./types/Bar');
const Foo = require('./types/Foo');
const SANCTUARY_CHECK_TYPES = envvar.boolean('SANCTUARY_CHECK_TYPES');
const S = sanctuary.create({
checkTypes: SANCTUARY_CHECK_TYPES,
env: sanctuary.env.concat([Bar, Foo]),
}); Advanced usage, after: const envvar = require('envvar');
const R = require('ramda');
const sanctuary = require('sanctuary');
const $ = require('sanctuary-def');
const Bar = require('./types/Bar');
const Foo = require('./types/Foo');
const SANCTUARY_CHECK_TYPES = envvar.boolean('SANCTUARY_CHECK_TYPES');
const def = $.create({
checkTypes: SANCTUARY_CHECK_TYPES,
env: sanctuary.env.concat([Bar, Foo]),
});
const S = R.map(f => f(def), sanctuary.unbound); Diff: const envvar = require('envvar');
+const R = require('ramda');
const sanctuary = require('sanctuary');
+const $ = require('sanctuary-def');
const Bar = require('./types/Bar');
const Foo = require('./types/Foo');
const SANCTUARY_CHECK_TYPES = envvar.boolean('SANCTUARY_CHECK_TYPES');
-const S = sanctuary.create({
+const def = $.create({
checkTypes: SANCTUARY_CHECK_TYPES,
env: sanctuary.env.concat([Bar, Foo]),
});
+
+const S = R.map(f => f(def), sanctuary.unbound); Is this what you have in mind, @rjmk? |
Yeah, that's a great idea. You're right about my imagined usage. I'd also like to like at some other use cases. After: const envvar = require('envvar')
const objmap = require('object.map')
const sanctuary = require('sanctuary')
const $ = require('some-other-type-checker')
const def = $(sanctuary.env)
const S = objmap(f => f(def), sanctuary.unbound) const envvar = require('envvar')
const objmap = require('object.map')
const sanctuary = require('sanctuary')
const curry = require('sanctuary-compatible-curry')
const def = (_, __, f, ___) => curry(f)
const S = objmap(f => f(def), sanctuary.unbound) const envvar = require('envvar')
const objmap = require('object.map')
const $ = require('sanctuary-def')
const Foo = require('./foo')
const Bar = require('./bar')
const fs = require('./fs')
const env = [Foo, Bar]
const def = $(env)
const gs =
{ x: def => def('x', ..., ..., f.x)
, ...
}
const U = objmap(f => f(def), fs)
U.env = env
U.unbound = fs (Apologies for mutating How possible are these with the old way? I think (1) would be achieved with using (2) is just the same as turning off checking. It just means that another module could implement currying that behaves the same as Sanctuary's (3) shows using the type checking for another module. This is compared to exposing a There might be some other cases worth looking at, but I thought I would help the ball continue rolling |
Could you explain the helper function in (3), Rafe? |
Sure. I missed an argument in the type signature. The idea is that const def = (name, constraints, expTypes, impl) => checkWrap =>
checkWrap(name, constraints, expTypes, impl) It's not much of a helper, but it's a nice way of reifying a proposed API |
Thanks for the clarification, Rafe. I'm going to wait until Sanctuary v0.12.0 is out the door before investigating your proposal further. Feel free to take the lead on this if you feel so inclined. :) |
Some ideas I've been meaning to put on
paperscreen.Definition of environment
Creation of functions
Functions are defined through the
def
function. It takes an environment, function name, constraints, type definition and finally, a function (body). It will return a function that is curried, and uses the environment for its type-checking.In most cases, we could partially apply
$.def
with our environment.This approach allows a user to build an environment first, allowing all
sanctuary-def
-based libraries to type-check the entire range of types a user uses:Sanctuary could have a utility that does this for you:
Adding polymorhpism
Since
sanctuary-env
andfantasy-environment
/bilby
have very similar solutions to different problems, I thought it might be nice to combine them.This would mean
sanctuary-env
created functions would not only be curried and type-checked, they would also be polymorphic.The downside is that it will become quite difficult to keep the API as described above. Every time a function is created that shares a name with one created earlier, a function that represents both should be created.
This means
def
would have to have access to earlier functions somehow. Fantasy Land solves it by returning a new environment that contains the function, rather than returning the actual function. It pays to move theenv
parameter to the end ofdef
if we take this approach:In this case, the returned object is the environment. It remains useful to allow users to create the environment in two steps; first types, then functions. The API for the user hasn't changed, but the way we define our functions internally has changed completely.
Users are now able to do the following:
Note One possible degradation is the
loosy
feature. How do we do the polymorphism without checking the input types? Perhaps this kind of feature is best kept for when the performance is no longer a concern.The text was updated successfully, but these errors were encountered: