#568 Enhancement: Type Inference on It Blocks

JohnDG Mon 4 May 2009

I'd like type inference on it-blocks, for all parameters, fields, and local variables.

// 3rd param is it block
foo(a, b, { c = d }, f )
|B| a := { c = d }
// Both of these are it-blocks:
a {
   b = { 
   }
}

tactics Mon 4 May 2009

Could you be a little more explicit about what you mean? I'm not sure what this would do exactly. What kind of uses do you have in mind for this?

JohnDG Mon 4 May 2009

It eliminates boilerplate and makes code much cleaner. Moreover, it makes Fan behave consistently with respect to it blocks.

Take the 3rd example. It would enable code that looks like this:

animate {
   background = {
       color = rgb(1, 54, 34)
       image = url("background.png")
       position = [px(12), px(29)]
   }

   opacity = 1.2
   fontSize = em(2.1)
}

which currently has to be written like this:

animate {
   background = |BackgroundProps| {
       color = rgb(1, 54, 34)
       image = url("background.png")
       position = [px(12), px(29)]
   }

   opacity = 1.2
   fontSize = em(2.1)
}

Why should I have to specify the type of background when the compiler already knows it? And why do I have to specify it here but not if background is the last parameter to a method?

Basically, this proposal just carries type inference for it-blocks to all the places they can be used, to make Fan consistent and to scrap boilerplate.

qualidafial Mon 4 May 2009

+1, I'm completely in favor of this.

tompalmer Mon 4 May 2009

It wouldn't be like this today (assuming background was previously assigned before the callback from animate happens)?

animate {
   background {
       color = rgb(1, 54, 34)
       image = url("background.png")
       position = [px(12), px(29)]
   }

   opacity = 1.2
   fontSize = em(2.1)
}

Would you really want to assign a configurator to a field (background) as you show in your example?

qualidafial Mon 4 May 2009

Would you really want to assign a configurator to a field (background) as you show in your example?

What if the field is const, or the object is immutable?

tompalmer Mon 4 May 2009

What if the field is const, or the object is immutable?

Then I think the current idiom would be like so?

background = Background {
  color = rgb(1, 54, 34)
  image = url("background.png")
  position = [px(12), px(29)]
}

And that does provide some redundancy in the form of needing to choose an implementation for the background, I admit. Still, John's earlier examples don't seem to match my current understanding of Fan common practices.

Maybe a lonely it-block, as John would like to see, should automatically create a new object calling make on the actual expected class? (Rather than creating a stand-alone function object.) In that case, you wouldn't be choosing a subclass. This encourages concrete code, but so far as I can tell, Fan encourages concrete much more than say Scala, so maybe that's okay.

brian Mon 4 May 2009

There are two issues here which I believe John has proposed:

  1. allow type inference for a closure or it-block on a method parameter regardless if it appears inside or outside of the method call's parenthesis
  2. enhance it-block type inference to work off left-hand of an assignment statement

I think issue number one makes good sense, there is no reason why type inference shouldn't work consistently inside versus outside the parenthesis.

The second request is a new feature. To simplify the examples, the way I understand the proposal is this:

class Foo
{
  Background bg
}

// this code
foo.bg = { color = Color.red }

// is sugar for
foo.bg = Background { color = Color.red }

Although regarding the other part of the example...

Foo
{
  bg { color = Color.red }
}

// that is already sugar for
Foo
{
  bg.with { color = Color.red }
}

So maybe I missed some subtilty in the proposal, but to me the key is type inference with assignment.

I can see allowing type inference based on lhs of assignment. However I do think it turns it-blocks into more object literals versus declarative closures to a method call since no type or call appears on the rhs of the assignment. Furthermore it breaks Fan's JSON like serialize syntax which explictly types all objects (although certainly type inference techniques could potentially be used in serialization too).

So my thoughts are that fixing the first issue is something we should do. However, I think it is too soon after it-block introduction to implement issue two. I'd like to get some solid experience under our belt with it-blocks the way they are now before tackling that.

tompalmer Mon 4 May 2009

allow type inference for a closure or it-block on a method parameter regardless if it appears inside or outside of the method call's parenthesis

Can it-blocks be used today at all except when trailing objects or method calls?

For issue 2, automake (with automatic class choice) for lonely it-blocks could be a cool feature, but I'm not sure that's what John was meaning. It might be safer to continue to be explicit with serialized objects, though, because having the meaning of data change behind your back is sad. Maybe a bit of explicit typing is nice.

However, for manually constructed code, I'd be less worried about the inference. Or maybe as the code base grows, the ability to call any code "manually constructed" (in the sense that you pay attention to it) becomes less and less.

And maybe it's no big deal for serialized data, either, but I'd have to think about it more.

JohnDG Mon 4 May 2009

The second request is a new feature.

No, it's not a new feature. In my example, background is actually a closure.

Here's a smaller, simpler example:

class BackgroundProps {
   Color color
} 
class Foo
{
  |BackgroundProps| bg
}

Foo {
   bg = {
      color = Color.red
   }
}

The above is the automatically type-inferred version of:

class BackgroundProps {
   Color color
} 
class Foo
{
  |BackgroundProps| bg
}

Foo {
   bg = |BackgroundProps it| {
      it.color = Color.red
   }
}

As you can see, this is not a new feature, simply an enhancement to the type inference engine. The designer of this API would call the bg closure on a BackgroundProps to create the desired BackgroundProps instance.

In my mind, there's no good reason to do type inference in some places and not others. Uniformity is key to memory.

However I do think it turns it-blocks into more object literals versus declarative closures to a method call since no type or call appears on the rhs of the assignment.

I don't agree. It doesn't alter the semantics of it-blocks. It just saves me from having to write crap the compiler already knows. Makes the code look a lot cleaner.

Furthermore it breaks Fan's JSON like serialize syntax which explictly types all objects

Why would that have to change at all? Just because you can leave out the type, doesn't mean you have to, or that it's even allowed for the subset of Fan embodied by the Serialization syntax.

brian Mon 4 May 2009

No, it's not a new feature. In my example, background is actually a closure.

That doesn't seem that useful to me - the common case is that the lhs is a normal class, not a function type. So I think if we supported it, then it wouldn't be straight type inference, but rather sugar for a ctor:

bg = BackgroundProps { ... }

It may then be BackgroundProps constructor then takes an it-block as a parameter, in which case it becomes sugar for:

bg = BackgroundProps.make(|it| { ... })

But a feature which only supports type inference when the lhs if a function type seems a lot less useful (for declarative programming).

JohnDG Mon 4 May 2009

That doesn't seem that useful to me - the common case is that the lhs is a normal class, not a function type.

The point is that Fan's inconsistent now. It does type inference function types in some places, but not others. Moreover, this isn't some imagined example, it's one I've run into twice. It's very useful to me because it lets me write declarative APIs.

So I think if we supported it, then it wouldn't be straight type inference, but rather sugar for a ctor:

That is indeed a new feature, and while I would certainly use such a feature, it would not replace my desire to have type inference on function types.

This works today:

class Foo { Int a }

foo := Foo

foo.a = 1

So does this:

Void bar(|Foo| c) { ... }

bar { a = 1 }

But this does not work:

class Bar {
   |Foo| c
}

bar := Bar
bar.c = { a = 1 }

This does not make sense to me. The compiler supplies the type in some cases and does not supply it in other cases. It's inconsistent and it constrains the manner in which I can write declarative APIs.

brian Tue 5 May 2009

Ok, to be precise let's clarify that what is being proposed is

  1. to allow type inference for closure/it-blocks when used as arguments to a method call based on the expected parameter (regardless of whether inside parenthesis or not)
  2. allow type inference for closures/it-blocks when used as the LHS for an assignment and the RHS is a function type

Does this capture everything that you are proposing John?

As I said I totally agree on issue 1.

I understand where you are coming from now on issue 2 - I can surmise from your example you are creating some declarative CSS like DSL based on builder it-blocks, so I can totally understand why you'd want this. Although I admit to being a little hesitant about enhancing the scope of type inference without more experience on use cases.

But I'd like to hear what everyone else thinks.

tompalmer Tue 5 May 2009

allow type inference for closures/it-blocks when used as the LHS for an assignment and the RHS is a function type

... or presumably anywhere an expression can go. I don't see much different between 1 and 2. They're both just expressions.

It-blocks can't even go in these places today, right? So it's not just about type inference here.

I still disagree with this. I don't think standalone it-blocks (if they are introduced) should act like normal functions.

I do think that normal function expressions should be allowed to do more type inference.

JohnDG Tue 5 May 2009

Does this capture everything that you are proposing John?

Yes. Basically, I'd like to be able to get type-inference for closures/it-blocks when used as arbitrary parameters, even within parentheses (not just the last parameter, outside the parentheses), and I'd like to be able to leave out the type on the RHS when the type of the LHS is statically known, as it is when doing an assignment to a field.

Void foo(|A| a, Bool t) {
  ...
}

foo( { it.b = c; }, true)

and

class Foo {
   |A| a
}

foo := Foo
foo.a = { it.b = c }

Fan already supports type inference for it-blocks and closures, in some contexts. This proposal just calls for consistent support everywhere.

I don't think standalone it-blocks (if they are introduced) should act like normal functions.

An it-block is an ordinary closure with a few special rules (i.e. implicit it parameter, which is used to resolve symbols).

qualidafial Tue 5 May 2009

  • +1 for items 1 and 2 as outlined most recently by brian
  • -1 for having orphaned it-blocks behave as syntax sugar for constructors.

jodastephen Tue 5 May 2009

I would expect to be able to type-infer into a function-type variable or method parameter.

JohnDG Tue 5 May 2009

-1 for having orphaned it-blocks behave as syntax sugar for constructors.

No one has suggested this (except maybe Brian), rather, this was a misinterpretation of my request. My request strictly has to do with type inference.

tompalmer Tue 5 May 2009

My request strictly has to do with type inference.

No. It also has to do with adding orphaned it-blocks to the language. That might be fine, but it's not just about type inference.

brian Tue 5 May 2009

No. It also has to do with adding orphaned it-blocks to the language. That might be fine, but it's not just about type inference.

Tom, can you explain this a bit more? To me, it does actually seem like a type inference issue where {...} is a special function that requires inference. The debating being under which situations type inference is allowed. Today only as an attachment as the last parameter to a method. The proposal being to allow inference for any parameter and as RHS or assignment.

Is your point, that the special {...} syntax should only be used as the attachment to the end of a method? I think I can see that viewpoint, since it-blocks are special in that when they don't bind to a parameter they are implied to be .with(...) which is very much a special thing that only happens by chaining to the end of a method call.

So I think you can make the case that with-blocks are not strictly based on type inference due to their special nature that they can be tacked onto the end of any expression.

tompalmer Wed 6 May 2009

So I think you can make the case that with-blocks are not strictly based on type inference due to their special nature that they can be tacked onto the end of any expression.

That's what I was saying. It can be fine to change that if you (or we, because you do like to consider general opinions) want, but I was just trying to make a point that this proposal goes beyond just type inference.

And part of my concern was that the standalone it-blocks can look so much like JavaScript object literals that John was making the parallel, and yet they would mean something completely different. That's not necessarily terrible. Just want to make sure it's under consideration.

Side note, orphaned it-blocks could actually be used as either functions or as constructor calls without ambiguity, since the inferred type would be either a function or some other type of object. So if we want orphaned it-blocks and choose just, say, the function meaning for them, we could still add the constructor call meaning in the future if wanted. They aren't mutually exclusive. (I'm not voting at the moment for either, however, though I personally sort of like the constructor-call feature.)

tompalmer Sat 9 May 2009

Maybe use this thread for orphaned it-block requests, and make another thread for generalizing the type inference on standard function expressions?

JohnDG Sun 10 May 2009

Is your point, that the special {...} syntax should only be used as the attachment to the end of a method?

I personally see it blocks as a special form of closure (indeed, that's what the unification proposal was all about). So it seems natural to me to want to use them wherever I can use closures -- and natural, again, that type inference would be available to me with both. (The difference with type inference for it blocks is that you don't even need to specify |x|, because it is assumed, which allows for a very clean declarative-style of coding.)

brian Mon 11 May 2009

I basically think what John has requested is correct, closure type inference and it-blocks should work on the lhs of assignment.

However, my preference would be to hold off on a fix. I tend to like to have several months of experience with a feature before tackling edge case fixes like this. I suspect there is still lots we need to learn about it-blocks, and I'd prefer to understand all the warts first before we dive into fixes.

Login or Signup to reply.