Open JAForbes opened 7 years ago
How about
// file1.js
// string -> StrMap Type -> ???
export default $.environment(({def, $, SumType}) => ({
TypeA: $.NullaryType(...),
TypeB: $.UnaryType(...),
TypeC: $.RecordType(...),
fn1: def(....),
fn2: def(....),
TypeD: SumType(...)
}));
// file2.js
// string -> Array ( Any -> Any ) -> ???
export default $.environment(({$}) => ({
TypeA: $.NullaryType(...),
TypeB: $.UnaryType(...),
TypeC: $.RecordType(...),
}));
// main.js
import env1 from 'file1';
import env2 from 'file2';
const makeNs = $.namespace({
def: S.create,
$: $.create,
SumType: SumType.create
});
const ns = makeNs('main', S.concat(env1, env2)); // env2 overwrites bindings from env1
const { bindings: {TypeA} } = ns;
const { TypeA: f1TypeA } = makeNs('', env1);
$.environment
or $.namespace
could also concatenate the bindings with whichever standard/global types/functions exist.
I like that revision!
Maybe $.namespace
could accept args for documentation base urls, and package name prefixes, and the namespace itself could autoprefix type and function names with package prefixes. Potentially we could just provided the hash, or final path of a documentation url per type.
makeNs(
'my-package/main'
,'https://my-package.github.io/docs/'
,{ checkTypes: true, env }
)
That would automatically prefix any documentation urls in that namespace with that base url, and any type with that package prefix.
Maybe $.namespace could be:
namespace :: Boolean -> StrMap Function -> String -> String -> Environment
The first argument is checkTypes
. I'm thinking it's going to be the most stable argument. Usually you're going to want this setting to be these same everywhere.
I don't like the idea of $.namespace
being stateful, so I like Gabe's revision. :)
I don't want to have to think about order of operations across possibly 100's of modules. I want to be able to write
$.RecordType({ ... })
and know I can use that type anywhere now.
This is the heart of the matter, I think.
I believe there is an approach which avoids the ordering problem:
def
function and S
module for use throughout one's application.def
and S
throughout one's application.Here's a concrete example:
// app/index.js
const {S, def} = require('./env');
...
// app/env.js
const S = require('sanctuary');
const $ = require('sanctuary-def');
const CountryCode = require('./types/CountryCode');
const User = require('./types/User');
const UserId = require('./types/UserId');
// checkTypes :: Boolean
const checkTypes = true;
// env :: Array Type
const env = S.env.concat([CountryCode, User, UserId]);
exports.def = $.create({checkTypes, env});
exports.S = S.create({checkTypes, env});
// app/types/CountryCode.js
const $ = require('sanctuary-def');
module.exports = $.NullaryType(...);
// app/types/User.js
const $ = require('sanctuary-def');
const UserId = require('./UserId');
module.exports = $.RecordType({
id: UserId,
firstName: $.String,
lastName: $.String,
});
// app/types/UserId.js
const $ = require('sanctuary-def');
module.exports = $.NullaryType(...);
There are certainly ways in which we could make this more convenient, but the most important thing is to first establish the correct module layering:
+-----------------------------------+
| Application |
+-----------------------------------+
| Environment |
+--------+--------+--------+--------+
| Type | Type | Type | Type |
+--------+--------+--------+--------+
- - - - - - - - - - - - - - - - - - - - - - -
+-----------------------------------+
| sanctuary-def |
+-----------------------------------+
If you agree with this layering, I'd love to know how well it works in your application, @JAForbes.
@davidchambers Yeah I like this style, I saw you demonstrated it in the gitter after this discussion had taken place over there and I thought it was brilliant!
I'm not sure if it works in the context of sum-type, we often want to define model types and page specific types in close proximity to the view because their helpful for modelling. And we will probably want to create functions using def
that are aware of these types, defined in the same file. So we end up needing to create new def
functions in each route.
I think it would be a shame to need to move these page specific types to a separate file.
The clean division your presenting is more difficult when we have hyper specific types for a particular page (model, enumerations of potential states and values specific to that page), not generic reusable types (like user, email, uuid etc).
If you read Elm code you'll see this pattern often, hyper specific types to a particular component.
It seems like our app specific sanctuary and sanctuary-def modules would be defined too early in this case.
I don't have any solutions, just voicing a concern 😄
If you read Elm code you'll see this pattern often, hyper specific types to a particular component.
This helps me to understand your requirements. :)
I suggest that you experiment in your application and comment again in this thread once you arrive at a satisfactory solution.
Great idea
-----Original Message----- From: "David Chambers" notifications@github.com Sent: 5/9/2017 3:00 PM To: "sanctuary-js/sanctuary-def" sanctuary-def@noreply.github.com Cc: "James Forbes" james.a.forbes@gmail.com; "Mention" mention@noreply.github.com Subject: Re: [sanctuary-js/sanctuary-def] (Optionally) automated environmentvia namespaces (#137)
If you read Elm code you'll see this pattern often, hyper specific types to a particular component. This helps me to understand your requirements. :) I suggest that you experiment in your application and comment again in this thread once you arrive at a satisfactory solution. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
I find myself calling
create
and passing around differentenv
's of types in many files, and I've only just started really leaning on sanctuary in a large project, and its going to get worse.I've thought about different ways to structure this to prevent the verbosity, but I thought I'd at least vent the radical thought that keeps coming to mind.
What I really want, is an automated env. I don't want to pass env's around, I want to define a type and ideally I want sanctuary-def to manage the concatenation for me.
This came up when I was working on sum-type, I wanted to be able to nest union types, and I wasn't sure if that meant I needed to insert the previously created types into the env of any new types. So to date (I think), that's exactly what I do. Its technically still pure, it just automatically gives each type a new env that includes the previously defined types. I don't know if I actually need to do that, but whether or not I do, it at least makes me wonder, why don't we do this by default?
I don't want to have to think about order of operations across possibly 100's of modules. I want to be able to write
$.RecordType({ ... })
and know I can use that type anywhere now.Now there's problems, maybe we don't want some subset of a project to include the environment from another part of a project. I think this is solved in programming languages via a
namespace
.So what if we had
$.namespace
And then in one place, we can initialise that
namespace
.Just walking through the signatures,
$.createNamespace
would accept a namespace name, and a list of functions that receive{ checkTypes, env }
,sanctuary-def
would somehow initialize them for us, and manage concatenating the environment behind the scenes. This is very hand wavey! 😄I'm not sure what
$.createNamespace
would return, I have a few ideas. One option, it could return a merged object of whatever each$.namespace
returned, essentially giving us an analogue to modules. Another idea could be to return an interface that gives the caller control over exception handling (whether to throw, or write errors to a stream etc)This API could let us do cool things, like use different namespaces for testing, or hot paths. Or control execution of code that depends on sanctuary-def types. Now we can toggle env for the entire namespace (across files) in one place. We also don't need to concern ourselves with concatenating different type arrays, because namespaces would automatically do that for us.
It's very opinionated, and maybe would be better off as a library, but I don't know if it would be possible to do this in user-land.
Its a very broad debatable idea, but seems interesting to me. I'd love to hear some criticism!