-
Notifications
You must be signed in to change notification settings - Fork 41
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
Better term for what we currently call Type or Type Dictionary #32
Comments
Algebras
// "algebra"
interface Monoid<A> {
empty(): A;
concat(x: A, y: A): A;
}
// "instance"
const Addition: Monoid<number> = {
empty() {
return 0
},
concat(a, b) {
return a + b
}
} |
Yeah, except what we call "Type" will be "Instance of Algebra" in that case. We actually introduce term "Algebra" already (that definition also has issues, btw). We can go that way. One problem though is that we would have to introduce "Algebra" before "Instance of Algebra". I think if we introduce concepts in that order it will be harder to understand for newcomers. Would be better if we found a term that doesn't depend on "Algebra", something that captures just a collection of some operations, not necessarily with some laws. |
Also I want to find a term that will work with fantasyland/fantasy-land#199 . Not sure "Instance of Algebra" will work well there. |
One more thought: this thing not even an instance of an Algebra, but collection of instances of Algebras. For example this: const Addition = {
empty() {
return 0
},
concat(a, b) {
return a + b
}
} contains an instance of Monoid and an instance of Semigroup. So at first level it's just a collection of operations. Then we can find instances of some algebras in that collection of operations. |
An algebra (or algebraic structure) is some set
where
such that An example of monoid is Now if we rename // this encodes the (A, *, u) monoid algebra
interface Monoid<A> {
empty(): A;
concat(x: A, y: A): A;
} And a particular instance of monoid over numbers is the following instance // this precisely encodes the `(N, +, 0)` monoid
const Addition: Monoid<number> = {
empty() {
return 0
},
concat(a, b) {
return a + b
}
} IMO it's not just a collection of operations, otherwise we should also say that For what concerns monoids and semigroups, a monoid is a semigroup with a unit element, then we can write interface Semigroup<A> {
concat(x: A, y: A): A;
}
interface Monoid<A> extends Semigroup<A> {
empty(): A;
} Groups are monoids with inverses interface Group<A> extends Monoid<A> {
inverse(a: A): A;
} such that etc... We can say that |
I totally understand what you are saying. We just can have more that one perspective on this, and we should choose a perspective that suits the best for communicating meaning of specification to readers. For example let's consider this object: const Addition = {
empty() {
return 0
},
concat(a, b) {
return a + b
},
subtract(a, b) {
return a - b
},
} First of all it's just a collection of operations, but on the other hand some operations in it form instances of algebras: Now we need to decide what perspective is more useful in spec. What we should try to capture in the name? The name should work well when we reference these objects in specification. We should be able to say "value must have a property that points to corresponding %our_name_for_these_things%", so we should also focus on what these things are in JS (objects interpreted as dictionaries). Also there is a concern about order in which we introduce concepts that I've mentioned before. Having all this in mind, I think we should capture in name a lower level essence of these things — just a collection of operations. Then in further section of specification we introduce algebras, that are requirements for these objects (like "it must have that method" and "this must equal that"). |
Maybe "Type instance"? In Fantasy Land's spec, it would unambiguously from context represent the actual instance, not the type itself. In Static Land's spec, it would unambiguously from context represent what is currently known as the "type object". I will point out that if you want to unite the two specifications, it's much easier to work with something a little more like Static Land than Fantasy Land, because from a library writer's standpoint, it's equally easy to implement either version, and in practice Also, many libraries already somewhat conform to Static Land in its current form by pure coincidence, because static methods are frequently easier to use than instance methods (no As for wrapping a Static Land type to an FL type, it's also much easier than vice versa (meaning, it's more flexible), and currying is much easier than with FL methods: function toFLType(T) {
function F(value) { this._ = T.of(value) }
function poly(ms, f) { ms.split(" ").filter(m => T[m] != null).forEach(f) }
poly("empty of chainRec", m => F[m] = T[m].bind(T))
poly(
"equals concat map bimap promap ap chain reduce extend extract traverse",
m => F.prototype[m] = function (...xs) { return T[m](this._, ...xs) }
)
return F
} |
I think "dictionary" is an appropriate term here. It's worth noting that for generality it's a good idea to drop the notion that these things are tied to a single type. For instance, here's a signature (psuedocode) describing a perfectly reasonable "set dictionary" that uses two types
Multiple types (to follow gcanti's examples) are covered by algebras via "indexed algebras" but that starts to get pretty hairy. Dictionary is used a little in Js contexts, but I don't think it sufferd as much from semantic collision as "module" does. It also lets you say "this dictionary relates types As a final note, it's worth keeping "runtime types" away from "compile-time types" to steer away from eventual confusions around coercions and type reflection. |
Also, you'll probably want to start considering the notion of "signatures", too. You use these terms like this
etc etc |
@tel SGTM. Might seem a little foreign initially to those less familiar with ML terminology, though. I'm fairly neutral on this, so as long as it ends up something more descriptive than "Type Representative" and less confusing than "Typeclass" (vs JS classes), I'm good. 😄 |
@isiahmeadows I agree, but this whole concept will take a little time to digest anyway, I feel. It turns OO/prototyping inside out anyway. Also, I want to go on record and say that "type representative" is outright wrong. These "dictionaries" or whatever aren't representing types. A type representative is a runtime value with the same structure as (a subset of) the static types of your program. They can be used for typesafe coercions and many of the use cases for runtime reflection without actually having reflection. |
I think we should remove word "type" from the name. This thing is not a type. Consider Addition/Multiplication example from the first comment. We have two almost identical objects they have same methods and work with same type, the only difference is the logic inside these methods. It's more like a strategy pattern from OOP.
💯 See fantasyland/fantasy-land#199 (comment) . I think the new specification should be structured more like static-land, with additional section about spec compatible values closer to the end. Although that's just my opinion.
"Dictionary" sounds like a too broad term. Although maybe I just need a little getting used to.
Yea, I was thinking that maybe we should introduce our own definition of "type". Which should be more like Flow/TypeScript types. In these systems a type can be for example "a set of all numbers and string 'five'" (
Could you define "signatures"? Not sure that I understand what you mean.
I think it works fine in current fantasy-land, because there it always tied to a single type as halve of the methods still live in prototype. But when we can have more than one dictionary for one type this doesn't work as well. One more thing I want to mention. Whatever name we choose we should keep in mind that these dictionaries may contain not only functions in the future. For example empty() / zero() may become just values, also we may need to add meta const Addition = {
empty: 0,
concat(a, b) {
return a + b
},
algebras: ['Semigroup', 'Monoid'],
} Looking at this I realize that the name "module" fits very well, maybe we should use it after all even though it already has another meaning. |
Here is a draft that I have in mind of how concepts can be introduced: Static typeHere goes definition of type similar to what used in systems like Flow/TypeScript. It should allow types like Module (temporal name)Module is an immutable dictionary (JavaScript object interpreted as dictionary and never mutated) that contains some functions and values. A module usually relates to some static type, which means that its functions work with values of a particular static type. We shouldn't complicate thing with notion of modules that relate to more than one type ("set dictionary" example). We probably wont need something like this for the spec. AlgebraHere we define an algebra as a set of restrictions for a Module. For example in order to support |
This is starting to sound really good. I'll reply on important quotes then summarize.
This is more or less necessary, though it can be done implicitly for a long time. "Static type" is, more or less, "a formal description of what we know about a variable/name at compile time". It's probably a good idea to keep these vague, but also simple. TypeScript's singleton and union types such as Of course, that's why an informal specification of the formal language of "what we know about a name at compile time" is probably best. Let people argue over informal semantics to make it stronger over time. That all said, the introduction of universal quantification and type functions is super, super, super useful. That's how we write a type like
Signature is nothing more than "type of module" in OCaml. For technical reasons, OCaml is made of two static languages. The first is "values and types" the second is "modules and signatures". Those pairings are completely analogous. So, for instance
defines a signature describing what modules implement an idea of comparison and a module which defines some operations on These are all OCaml, SML, ML ideas. Indexed algebras are not. They're just an abstract algebra mathematical idea.
Yep! In that way, they really are serving a totally different purpose from these "dictionaries"/"modules" described here. They're useful as they bring the idea of passing in static types to runtime functions, but basically orthogonal.
This is also very true. What's perhaps more interesting is whether you believe they must contain values (functions or otherwise) which relate to the type at hand. For instance, consider this signature
Is
I've been using a trick this whole time where I actually introduce a type variable within the module. This means that signatures "bind" anonymous types and types of values together and then modules "inform" how those relations exist. Consider MONOID
This let's me talk about the abstract idea of a Monoid in the same language for which I might write a module specific to addition
it also generalizes neatly to the modules which involve multiple types scenario.
While this is true, you're going to quickly run into an issue as Module/Signature resolves this in the same way that type systems always do. Generally, I think this is all heading in a very positive direction. I think the idea for the project itself is great—many of the challenges of applying mathematical thinking to OO cannot really be resolved, so these external "modules"/"dictionaries"/whatever move to a richer way of talking about programming. I've obviously been a big proponent of stealing from OCaml. Perhaps less obviously, I'm actually very happy with striking out in a new direction somewhat as Javascript is not OCaml and doesn't need to solve all of the same problems. Furthermore, it has new opportunities arising from not actually being type checked. We're not bound to strict inference algorithm restrictions as humans do our inference and are often dogged and ingenious. But it's very worth studying the practical elements of OCaml's system since they've solved a lot of problems and landed in a very nice place. The more that experience can be absorbed the further this project can go. |
functions vs values
Note that in PureScript class Semigroup m <= Monoid m where
mempty :: m
With functions, implementers may choose to be very strict in terms of type checking. Here's the // Monoid.js
export function empty<A>(): Maybe<A> {
return Nothing
} then you get an error if you try to mess with different monoid units import type { Monoid } from 'flow-static-land/lib/Monoid'
import { stringMonoid } from 'flow-static-land/lib/Monoid'
import type { Maybe } from 'flow-static-land/lib/Maybe'
import * as maybe from 'flow-static-land/lib/Maybe'
function f(m: Monoid<*>) {
return m.empty()
}
const u1 = f(maybe.getMonoid(stringMonoid))
const u2: Maybe<number> = u1 // <= error: string. This type is incompatible with number or you can choose to be more loose // Monoid.js
export function empty(): Maybe<any> {
return Nothing
} If // Monoid.js
export const empty: Maybe<any> = Nothing meta field algebra
Not sure why, for runtime type checking? It seems an implementation detail, AFAIK concepts Types are just sets (Flow/TypeScript syntax would be nice since we are targeting JavaScript) An algebra
Note that in general an algebra is related to one or more sets (e.g. a vector space). An algebra (OCaml signature, PureScript type interface Alg<A, B, ...> { // <= sets go here
op1(); // <= operations go here
op2();
...
} An intance (OCaml module, PureScript |
@gcanti Interfaces don't quite work the same as signatures since they cannot have type members. Type parameters do get a lot of the same work done, though. Type's aren't really just sets. They cannot be for foundational reason and shouldn't be for logical reasons. But that said, informally it's not too bad to unify the two. |
That makes sense, but still we may need more elaborated definitions to explain some concepts. But to try to avoid it and to use a vague definition for now sounds like a good idea.
This have to be allowed. Even now we require something like this for a lot of methods. Our methods may accept other types as arguments and produce other types. For example Setoid's Maybe we should just omit notion of relation to a type in the "Module" section, and say that module is just a collection of values and functions. We can then introduce relation to a type in algebras. For example "module
I still like "algebra" more. It's more familiar to people. Also signatures can be confused with method signatures, although we can call them "module signatures". And what also good about using "algebras" is that we can unite requirements for presence of methods and for laws in a single concept. But we also can define signatures in a way that includes laws. So yeah, worth considering!
Yeah, I'll remember that if we ever consider that change. Not considering it now, just wanted to mention as an example.
This may be needed in libraries like Ramda. Also for automatic generation of derived methods. So yeah, basically runtime detection. Can be useful sometimes.
One more issue with defining types as just sets is that we might need notion of types with type arguments, so we might need a more elaborated definition. |
I agree, but it's an interesting case. In particular, I think mines more interesting than your setoid example—it has to do with values which are associated with a "module" that is associate with a type but these values have types which do not reference the associated type at all. Weird! I agree that "module signature" and "function signature" can be a little confusing. That said, I typically don't think of function signatures as real things since the deeper idea is that "all names have types" and "functions are represented by names" so functions have types just the same as |
So getting back to the original issue. We have "Dictionary" and "Module" as suggested names. Maybe someone has any other ideas? We could also go with "Algebra" as @gcanti suggests, but this would require turning structure of specification inside out, and I just don't have a good picture of how it will look like. |
I've created a PR #41 . |
"Type" is the central artefact in the spec, but the name is confusing. The word "type" already has a different meaning in JavaScript, we have types like Array, number, Promise etc.
Fantasy-land recently introduced term "Type Representative". And if we use it in static-land that will be an improvement, but still not good enough. The word "representative" is good, this thing certainly represents something. But does it represent a type? For example what if we have two representatives:
What types they represent exactly? Do they both represent
number
? That doesn't sound right. What they actually represent are collections of operations on typenumber
.Addition
represent one such collection andMultiplication
another one. So a correct name would be "Collection of Operations Representative", but that is a terrible name of course.As far as I understand in OCaml similar things are called "modules", that would be a good name if that word didn't already have a different meaning in JS community.
Any suggestions on a better name?
This was previously discussed here #30 (comment) .
/cc @tel @isiahmeadows
The text was updated successfully, but these errors were encountered: