Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

operations on predicate functions #383

Closed
gavinking opened this Issue · 13 comments

5 participants

@gavinking
Owner

I'm not sure if this should go in the language module or SDK, but I think we should have functions for doing logical operations on predicate functions:

Boolean(T) not<T>(Boolean(T) p)(T t) => !p(t);
Boolean(T) and<T>(Boolean(T) p, Boolean(T) q)(T t) => p(t)&&q(t);
Boolean(T) or<T>(Boolean(T) p, Boolean(T) q)(T t) => p(t)||q(t);

The question is: where should this stuff belong?

@gavinking gavinking was assigned
@gavinking
Owner

P.S. it's sorely tempting to add operators for these, perhaps

0.lessThan [&] 10.greaterThan
[!] x.equals

Or:

0.lessThan .&. 10.greaterThan
.!. x.equals

Or:

0.lessThan (&) 10.greaterThan
(!) x.equals
@gavinking
Owner

Compare to:

and(0.lessThan,10.greaterThan)
not(x.equals)
@pthariensflame

This would be a perfect use for type classes, yes? Then you could just have Callable<Boolean,Args> given Args satisfies Anything[] implement the type class BoolLike, and make the operators as polymorphic as any other.

EDIT: Even better, you could have:

Callable<Return,Args> is BoolLike given Return is BoolLike given Args satisfies Anything[]
@gavinking
Owner

This would be a perfect use for type classes, yes?

Well, not from my way of looking at it. From the point of view of a language with subtyping, a Haskell-style type class gives you two things:

  1. the ability to be able to retrospectively introduce a type to an existing type (for example, to say that Boolean(T) is BoolLike), and
  2. the ability to define a type member (as opposed to an instance member) as part of the type definition (for example, to say that the type Ring has members zero and one).

We decided quite long ago that we didn't want introductions (1) in the language because it lets you circumvent so many engineered-in restrictions of the type system and opens up the potential for code that is really difficult to understand.

OTOH, I would love to have (2), which is necessary if you're going to define some very basic mathematical objects such as groups and rings. So when I talk about "type classes", I'm really talking about (2), and being able to write stuff like T.zero with given T satisfies Summable<T>. But this is not relevant to the problem at hand.

So, sure, introductions (let's not call them "type classes" for the purposes of this discussion) could solve this issue, but in fact they're not even necessary. For example, I could define & to mean and(p,q) in terms of this function:

B and<B,X>(B p, B q) given B of Boolean|Boolean<X> => ... ;

This is well-defined since Boolean and Boolean<X> are disjoint types.

By the same token, there's no real reason why the language spec couldn't simply overload the definitions of &, | !. Given the disjointness, I don't see any potential for ambiguities.

@gavinking
Owner
Callable<Return,Args> is BoolLike given Return is BoolLike given Args satisfies Anything[]

This ("conditional inheritance") is something @RossTate and I have discussed several times. I think we both quite like the idea.

@pthariensflame

You could even preserve the spirit of "operator polymorphism" by simply having Boolean inherit from Boolean<Boolean>, although I think that demonstrates why name overloading is just as bad at the type level as at the value level. :)

@gavinking
Owner

@pthariensflame In fact that's non-crazy. What would be wrong with having true and false be considered constant predicate functions, making Boolean a Boolean(Anything)?

P.S. Boolean(Anything), not Boolean(Boolean).

@pthariensflame

Ah, I see that it was a simple mistype on both sides, then; I thought perhaps that Ceylon allowed C#-style "type overloading", but I see that I was happily mistaken.

The thing is, any constant value can be treated as a constant predicate function, of any argument type or number of arguments; all you're really doing is adding an implicit reader monad to the context (on top of the existing implicit region, state, reflection, and restricted-option monads that Ceylon already, conceptually, has).

There's no denying the utility of doing so in this particular case, but it leads very easily down a rabbit hole:

  • What about numbers? Isn't just as useful to be able to write Integer(Integer) f = (g1 + g2 + g3) / g4 rather than Integer f(Integer x) => (g1(x) + g2(x) + g3(x)) / g4(x), especially when longer names are involved?
  • What about method calls? If I can do that with booleans and numbers, what about strings? Why can't I write Integer(String) f = g1.length - g2.length rather than Integer f(String s) = g1(s).length - g2(s).length

Where do you stop? And what happens to readability? Not to mention the possibility of other indubitably useful monads becoming implicit, like asynchrony: look what happened to C#'s async methods.

@RossTate
Collaborator

In my experience, it's better just to use functions directly. It works for all variations of the problem (e.g. constants), and it's never confusing for an outsider to read. One problem that arises is that it's not clear when to stop lifting. It works fine for operators because they are never defined for functions, but once you insert non-operators into the mix then ambiguities can arise. I can imagine situations where lifting would really improve readability, though. Maybe something like Integer(String) f = lifting(g1,g2) g1.length - g2.length would work?

@gavinking
Owner

Where do you stop?

Well, sure. I agree. I would definitely never want + and - to apply to functions.

@tombentley tombentley closed this issue from a commit
@tombentley tombentley Fix #383 f7b2fd1
@tombentley
Collaborator

Added an xor() too, hope that's OK

@chochos
Collaborator

It's a good thing you didn't go for the dick operator .!.

@chochos chochos referenced this issue from a commit
@chochos chochos js #383 b19d23a
@gavinking
Owner

Added an xor() too, hope that's OK

Is it actually useful for anything?

@chochos chochos referenced this issue from a commit
@chochos chochos fix #383 6245e8d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.