COMMUNITY

Structural typing - type compatibility

Ok, thanks, I’ll try to add it there (although the evolution page says that new features have to go through proposals, which very few users will ever bother with all that ceremony, just to be told something like “Yeah, we don’t like the feature, so I don’t know what to tell you”).

This whole thing leaves a bad taste in mouth and I won’t touch this subject anymore, I’m just sad to see the language with so much potential literally dying and doing everything they can to chase the new users away.

Here’s a little secret: the vast majority of features/changes don’t go through the evolution process.

The process is not there to annoy people (although it’s easy to think). It’s main reason of existence is that very often features get proposed in a very vague way, and 10 different people throw in 50 different ideas about design and implementation and it therefore becomes unclear what’s even being discussed. Haxe evolution proposals avoid this by having one design document that nails things down.

But I really have to wonder what other language you’re holding up as a benchmark, where all the feature requests are so readily implemented, even when presented by newcomers with lousy attitudes. In all honesty, you don’t come across as somebody who wants help, but rather wants to blow off steam … in other people’s face. So well … good luck :wink:

3 Likes

The only reason I even touched this subject is because I was thinking there’s some small, remote chance that something constructive could come out of it. Like I said, I won’t touch it anymore.

You’re the only one that offered something constructive in this thread. I tried to give you some background, but you keep asking me the same question. Why the reason for asking this question can’t simply be - I wan’t to see if that works in Haxe? This type of things is not really uncommon in languages with good&flexible support for structural typing.

Hey, Not sure if this helps, but adding parameterized types could solve the issue of static extensions.

public static function fn<T:A>(b:T) {}

Also kinda offtopic, in Haxe 4 instead of:

typedef BT = {
    > A,
    ?b:String,
}

You can use this, which looks more readable :slight_smile:

typedef BT = A & {
    ?b:String,
}

This whole thing leaves a bad taste in mouth and I won’t touch this subject anymore, I’m just sad to see the language with so much potential literally dying and doing everything they can to chase the new users away.

I hope you still going to enjoy Haxe, it has it quirks but in general you can work around many problems easily. The community is relative small but that doesn’t make the tool itself “dying”, it’s pretty alive actually. The var b:B = cast a; for example could do the trick, but indeed this can feel nasty.

3 Likes

Thank you for being nice and constructive. All I was trying to say was that if replies from “Haxe Team” would look like that, users would be much more inclined to stay in participate in discussions and in the ecosystem.

Yes, with “dying” I was referring more to the ecosystem than the language itself, but without the ecosystem, the future of language also can be seen as questionable.

The language exists for 10 years and we don’t see massive drop or growth so this is not entirely true.

I am interested to see an example of “sane structurally typed codebase”, because I don’t use structural subtyping often so I am not sure what sane means in this context…

Yes, maybe I should’ve used other word than “dying” - the ecosystem is not really growing, there’s no new libs, many of old ones are abandoned, etc.

I think not many people put their libs on haxelib anymore because you can just require them from different sources like GitHub too. I see quite some libraries related to tink, coconut, heaps, kha, openfl popup every now and then, so not sure thats true either :slight_smile:

Well, if you have two structures with the field with the same name, but different type and they could end up being using in the same context, I wouldn’t call that sane, would you?

OK, I don’t know the ecosystem very well, I was saying what was my impression from browsing the haxelib directory. Many new users will do the same.

In that case I think you want to unify B into A (example below) instead of the other way round (your original example)?

class Main {
	static function main() {
		var b:B = null;
		var c:C = null;
		
		foo(b); // ok
		foo(c); // ok
	}
	
	static function foo(a:A) {
		trace(a.a);
	}
}

typedef A = {a:String}
typedef B = {>A, ?b:String}
typedef C = {>A, ?b:Int}

No problem, its kinda hard to find your way through everything that’s available in the ecosystem, since it has many directions. You can also discover Haxe libraries on GitHub here https://github.com/topics/haxe

1 Like

Yes, but since B has only optional additional fields, in that sense they are polymorphic (i.e. if you do the trick with abstracts and implicit casts, everything would work as expected).

I would argue that the following is just half of the equation:

var a:A = { a: "a" };
// var b:B = a;        // error
var b:B = cast a;   // compiles, but probably next line is better
var b:B = { a: a.a };

The other half of the equation is that the following works:

function foo(o:{ a:String }) trace(o.a);
function bar(o:A) trace(o.a);

foo(a);
bar(a);

foo({ a: "foo", b: "bar" });
bar({ a: "foo", b: "bar" });

// and even
foo(b);
bar(b);

Since that works at function level, in the general equation of greatness, not being able to directly write b = a is not really crippling.

No?

P.S.: the greatness of Simn doesn’t always shows in his short comments, don’t let that fool you! Oh no, don’t. First, it’s out of topic :slight_smile: . Second, it’s a terrible mistake, he is actually a very nice person.

Yes, I know that explicit casts and defining structures inline works, I mentioned that. The problem is that using both of these things becomes very repetitive after a while.

IMO the real, clean solution without breaking anything for these things would be the support for different types (i.e. something like Flow’s exact and inexact types). But I’m not going to request that, because it’s not an easy or simple request and implicit casts can work as a workaround.

The point is to be able to directly use the functions which are defined this way (I hope that my second example makes that clear). Of course that just writing a=b on it’s own is not very useful.

Some of my replies to some people in this thread probably are too harsh and I apologize to them if their intention was not to be rude or snarky.
With everything that is going on, people are understandably more irritable and I think we all should try to be more understandable and more nice to each other. Especially in a small community like this.

2 Likes

The most important underlying issue here (which I tried to highlight few times, but it got sidelined every time) IMO is this: Almost every dev I know had same exposure to Typescript or Flow. By far, the easiest way I can get them to try Haxe is to tell them to try to use it as an alternative to Typescript/Flow. I want to be able to tell them: The support for structural typing is not as advanced as in Typescript/Flow, but Haxe is multi-paradigm language, the community is experienced in (or at least open to) different paradigms and you won’t have problems with trying to get help if you need to do something more advanced.

I just hate the dogmatism I see on each side of typing spectrum. For some type of problems, strict nominal typing is the best solution, for many others it’s the worst. And the other way around with structural&duck typing.

btw. If anyone is confused/unfamiliar with this terminology - structural typing, in it’s most basic sense (which I’m using here), is basically just a duck typing with various degrees of help from the compiler. There’s a lot of confusion and different interpretation of this topic, that’s why I was focusing on the concrete example.

What about something like this?

import haxe.macro.Expr;
import haxe.macro.Context.*;

typedef A = {a: String}

typedef B = A & {?b: String}

macro function inexact(e: Expr) {
  final expected = getExpectedType();
  final current = typeof(e);
  // We unify one way so we accept unsoundness
  if (unify(expected, current)) return macro cast $e;
  // This is not compatible at all, so let the compiler handle errors
  return e; 
}

function main() {
  final a: A = {a: 'a'}
  final b: B = inexact(a);
  final c: B = inexact({a: 1}); // Compiler error: Int should be String
}
1 Like