Skip to content
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

Module-level functions #24

Merged
merged 15 commits into from Sep 21, 2018
Merged

Conversation

nadako
Copy link
Member

@nadako nadako commented Jul 18, 2017

Allow defining functions (and maybe vars) directly in .hx files instead of making a class with static methods.

Rendered version

@Justinfront
Copy link

So the proposal is to allow stuff like this?

package;
// import Main.hello;
class Main {
  static public function main():Void {
    hello();
  }
}
function hello(){
  trace('hello function');
}

Could main even be just a function if user wanted is that part of your proposal?

package;
function main():Void { 
   hello();
}
function hello(){
  trace('hello function');
}

So essentially in more general case this:

package myStuff;
class MyFunctions{
   static function hello(){
      trace('my function hello');
   }
}

could be written:

package myStuff;
// package myStuff.MyFunctions;
function hello(){
      trace('my function hello');
}

Interesting proposal if I have understood but seems kind of like icing for the user especially if it worked like below, your just kind of removing the 'class' word.

package myStuff.MyFunctions;
function hello(){
      trace('my function hello');
}

but then you could not also put a class in the file, and could you have two groups of functions in a file and import them separately surely the extra of a class makes little odds? Is it not acceptable if no constructor to allow users to drop using static accept that I guess via reflection you could still create a class so you would need different holder name.

package myStuff;
functions MyFunctions{
   function hello(){
      trace('my function hello');
   }
}

Which seems a lot of change just so that users can avoid word 'static' and looks pretty the same as now.

I think the proposal could do with samples of how code might look atleast in your prefered solution, it would make it clearer for people who struggle to read technical definitions but are fine seeing code patterns.

So would constants or variables be in these packages ?

package myStuff;
const helloText = 'hello';
function hello(){
  trace( helloText );
}
function hello2(){
 trace( helloText + ' ' + helloText );
}

@nadako
Copy link
Member Author

nadako commented Jul 19, 2017

Could main even be just a function if user wanted is that part of your proposal?

Yes, I mentioned that. In fact, it'll automatically work if the implicitly created class for functions will be the primary module class.

your just kind of removing the 'class' word

That's the intention - when you don't do OOP, this class wrapping becomes just a visual clutter. And it's not just the class keyword. It's also class name duplicating the module name, braces, static keyword for each function, extra indentation level etc.

but then you could not also put a class in the file

That's not true. This proposal allow for placing module-level functions/vars along with other types just fine, that's the idea.

would constants or variables be in these packages ?

it's mentioned in the proposal as well, that's one of the open questions, but i think it wouldn't hurt to support that as well.

the proposal could do with samples of how code might look

thanks, I added a sample. however you seem to have read the proposal carelessly, because most of what you're asking about was mentioned there... maybe it's because of my english though. :)

@benmerckx
Copy link
Contributor

benmerckx commented Jul 19, 2017

Allow marking those as private as well? I think TDFunction(name:String, fun:Function) wouldn't allow for that as Function doesn't have the needed access?

private function rand() return Math.random();
var MY_RANDOM_NUMBER = rand();

Edit: thinking about it metadata and docs would be useful as well.

@nadako
Copy link
Member Author

nadako commented Jul 19, 2017

Allow marking those as private as well? I think TDFunction(name:String, fun:Function) wouldn't allow for that

Yes, private functions should totally be supported, though it just occured to me that haxe.macro.Expr structures doesn't have a notion for other private types either, so I guess that's something that should be added to the TypeDefinition structure instead of TypeDefKind constructors.

@frabbit
Copy link
Member

frabbit commented Jul 19, 2017

Maybe the easiest way to implement this is to put all top level functions as statics automatically in the module named class and disallow the definition of a class named as the module itself. This way it would only be nice sugar for this common case.

@nadako
Copy link
Member Author

nadako commented Jul 19, 2017

Maybe the easiest way to implement this is to put all top level functions as statics automatically in the module named class and disallow the definition of a class named as the module itself. This way it would only be nice sugar for this common case.

Yes that's what I proposed. I also propose a more complex option, but I think we should settle with this.

@Justinfront
Copy link

The example helps thank you. Your english is fine, code examples really are much clearer than words for some of us, even if they don't cover all the detail.

I don't really understand what frabbit said maybe a specific example would help.

Looking at the example you added, I am still not clear on how you would prefer/imagine package/import will work in practice, the example glosses over thier use, could you add an example of your prefered package and import use. For instance if I had a file MyMath, and you have a group of functions called trig, and a group called matricies in the MyMath file, and you wanted to use one in one class and one in another, what would that actually look like, or is that not supported?

I understand the proposal is for functional approaches to be more clear, but it seems it is just sugar. I don't really see it adding much beyond creating 2 ways to do one thing, which will make learning more complex overall - even if initially it's simpler, your adding a lot of new - and it all needs explaining, even something like lots of slightly different ways of doing 'if' add to the amount people need to learn before they can be productive.

The proposal could make for cleaner haxe code and make it clearer that Haxe has functional leanings, but it's hard for me to see it as more than a conceptial icing change, I don't see the bigger picture of how this is more than just reducing keystrokes maybe that's enough but not historically!

@Aurel300
Copy link
Member

Interesting. It is basically syntactic sugar, but so was the arrow notation for functions and people were pretty happy with that. Some more issues should probably be clarified:

  • How do you treat static extensions?

Typically e.g. using Lambda; would look through all public static functions in the class Lambda in the module Lambda. With your proposal, does this include the classless functions? As I understand it, they are implicitly static.

// Lombdo.hx
function double(x:Float) return x * 2;
// Main.hx
using Lombdo;
static function main() trace(Math.PI.double());

Should this compile?

  • Macro context?

Can the module-level functions be declared macro? How do you use them as build macros?

  • Reflection?

Is there any way to access the module-level functions of a module with reflection? Currently the reflection and type APIs can only handle classes and instances, I don't think they have a notion of modules. Is a function declared at module level a function instance? What is its lifetime?

@markknol
Copy link
Member

markknol commented Jul 20, 2017

This would make explaining Haxe and documenting it much easier, most examples don't need classes and could run/tested this way. Making small tools would also be easier since a main function and maybe some other function can already be a full Haxe application. Its not that big deal to wrap a class around it, but if you can leave it out, why not.

My only concern is that I think you directly want module-level variables too, otherwise that will confuse ("why can I use function here, but not var here?").

Also try.haxe could remove it's "simple" mode, which now wraps everything in a class with static main function. @clemos

@nadako
Copy link
Member Author

nadako commented Jul 20, 2017

@Aurel300 since the idea is to place module-level functions in an implicitly created class (so they end up being its static methods), they are going to work just fine as macros and static extensions. and reflection is a good point in favor of making that class the primary module class (so you resolveClass the module name and access its statics as usual).

@clemos
Copy link

clemos commented Jul 20, 2017

@markknol: on try.haxe, it's indeed just a matter of shortening code samples to a minimum, not at all about advocating for a more "open" way to define functions or variables.
I must say I'm quite used to the current way (incl. with import MyStaticHelpers.*) and find it convenient as is.
Actually, I think that introducing this could pollute the toplevel namespace, potentially importing a lot of (unwanted) toplevel identifiers.
Also, this would add yet another complexity level to identifier resolution.
Finally, I'm not sure I understand how this proposal relates to functionnal programming, other than making it easier to avoid the Java-sounding class keyword :trollface:

@Aurel300
Copy link
Member

@clemos I can see the appeal functional programming. A lot of times, I would like to do something like arr.map(flatten) or zip(arr1, arr2) or arr.map(id), etc. Lambda in std. does something similar, when you use it as a static extension. However, because it is a static extension, it will only ever be available as a method of a object, i.e. you have to access it with the dot operator. With FP-like functions it is often needed to be able to access them anywhere, without having an instance – because, after all, you might want to pass a function to a higher-order function. So compare:

arr.map(a -> a.flatten())

And:

arr.map(flatten)

Other functions, like zip, don't really make sense when accessed as a method of one of its two arguments.

So yes, importing a module with a bunch of functions like this would "pollute" your namespace, although naturally any variables or functions you define locally override the imported ones. However, there are situations when you are writing very FP-focused code and it would be beneficial (for legibility and nice syntax) to actually have them in the namespace.

@fullofcaffeine
Copy link

Hey guys, can we proceed with this one? I think we have enough 👍 for it to be merged.

@markknol
Copy link
Member

The core team should decide here.

@Pauan
Copy link

Pauan commented Jul 25, 2017

This is one of the few things preventing me from switching from TypeScript to Haxe, so this gets a big 👍 from me.

Haxe provides a lot of features supporting functional oriented paradigms (most importantly first-class functions)

In addition to first-class functions, Haxe also has ADTs (e.g. enums) and pattern matching, which is very unusual for an OOP language (but definitely a good thing!)

Haxe definitely has the potential to be an excellent multi-paradigm language, supporting both OOP and functional style seamlessly. It just needs a little nudge in the right direction (e.g. this proposal).

While this proposal describes module-level functions, I think it would be logical and consistent (although less useful) to also allow module-level variables, treating them similarly static vars.

If functions are "elevated" into static methods of an implicit class, couldn't top-level var be elevated into static members of the implicit class?

I think top-level var will absolutely be needed: to define module state (e.g. a counter):

var counter: Int = 0;

public function increment(): Int {
    return ++counter;
}

Or to define static data structures which are shared between functions:

var foo = {
  // various properties defined here
};

public function bar() {
  // do stuff with foo
}

@nadako
Copy link
Member Author

nadako commented Jul 25, 2017

Yes, I mentioned toplevel var in the original proposal and I think it should be done as well. I also now think that it should simply become an implicitly created main module class with static fields. I'll update the proposal tomorrow to be more clear about it and maybe provide an sample implementation.

@fullofcaffeine
Copy link

As per your proposal, we will have a way to execute code at the module? We will be able to call functions at the module-level?

@nadako
Copy link
Member Author

nadako commented Jul 26, 2017

@fullofcaffeine if you're talking about arbitrary module-level expressions, then no, it's only about defining module-level functions and vars

@fullofcaffeine
Copy link

Fair enough, I guess module-level expressions would just overcomplicate things, since we would have to assume code would be executed upon importing modules, which could lead to unwanted side-effects. I think the proposal is good as it is.

@back2dos
Copy link
Member

Haxe provides a lot of features supporting functional oriented paradigms (most importantly first-class functions), however it lacks a clean way to actually define functions without creating a wrapping class. This is annoying and gives a feeling of bloatedness to new people coming from non-OOP background. [emphasis added]

Everyone with a decent understanding of functional programming will consider nesting functions in a class as a minor nuisance at best. Even Scala requires global functions to be defined on some object. People drawn to functional programming will choose Scala over Haxe for a pretty long list of reasons, none of which is about having to type a keyword more or less.

The benefits of this proposal seem negligible to me, as opposed to the complexity added. If you want to do FP practitioners a favor, that's much appreciated, but this just ain't it ;)

@fullofcaffeine
Copy link

fullofcaffeine commented Aug 17, 2017 via email

@Justinfront
Copy link

Justinfront commented Aug 18, 2017

Would allowing Main.hx to have the hxml commands above package; feel more scripty especially if there was a haxelib to generate a range of these single file templates along with the function changes. It's nice to have a single file for scripts and having a compile.hxml and Main.hx separately is not so ideal. I can't see that it would not be possible to allow/implement a mixed Main file? But doubt if anyone would think it is a good idea beyond me :) sorry off topic. I would not be something you would use for more than scripts and tests.

@hughsando
Copy link
Member

Maybe not exactly hxml, but starting with the unix "#!" might work - all the parser need to do is ignore it, like:

#! haxe -lib A -D B --run
trace("Hello!");

@Pauan
Copy link

Pauan commented Aug 18, 2017

@hughsando Linux only allows for a single argument in the shebang, so that won't work.

Nix works around that limitation by doing this:

#! /usr/bin/env nix-shell
#! nix-shell -i real-interpreter -p packages

The second #! line describes the actual parameters. Haxe could do something similar.

Nix already supports both Haxe and haxelib, so if Haxe ignored all lines starting with #! then it would be possible to write Haxe shell scripts in a single file using Nix + haxex:

#! /usr/bin/env nix-shell
#! nix-shell -i ./haxex.sh -p haxe

class Main {
    static function main() {
        trace("Hello world!");
    }
}

But this is starting to get very off-topic, I think that should be in a separate proposal or issue.

@jcward
Copy link

jcward commented Aug 18, 2017

Hmm... Haxe is certainly more convenient than Javascript in many regards,
and still, Javascript is considered to be a scripting language. Would you
mind to elaborate on this statement?

Sure. My types of "scripting" tasks are typically shell scripts. Running external commands and munging (aka processing) text files.

So I imagined myself a random scripting task -- count all the "x" characters in all the .hx files in the haxe repo. I timed myself, and literally took less than a minute to do this in irb:

> sum = 0; `find . -name "*.hx"`.split("\n").each { |f| sum += File.read(f).split("x").length-1 }; sum
30022

Now, there're all kinds of questionable practices there. And it may look like Greek to many, but the process of constructing such a program [in a REPL, with Ruby language features] is insanely fast. I answered my question in less than a minute. That's what a quick scripting language does for me. Haxe -- I can't imagine how long or how many lines of code it'd take me to do this (in fairness, partially because I don't do this type of thing in Haxe.)

But I use Haxe to build large, reusable systems. Without compile-time type safety, Ruby and JS are a nightmare at that. They're different tools. I use both. It would take massive syntax / paradigm changes for Haxe to beat the convenience of Ruby at these kinds of scripting tasks. Skipping class boilerplate is barely a drop in the bucket. Meh.


Ok, for comparison's sake, I coded the above in Haxe. Here's what I ended up with: https://hastebin.com/zeyudaruci.cs

Ignoring the time I spent looking up various APIs (which wasn't really Haxe's fault), there are still a bunch of minor inconveniences that all add up:

  • I had to create a new directory and Main.hx file
  • I had to write some class boilerplate
  • The sys.io.Process cmd/args syntax is less convenient, and I had to check the arg quotation.
  • The p.exitCode() line in explicably hung for tests with a lot of files to scan... hmm. wasn't sure if I needed to read the exit code to ensure stdout was ready, saw it in some exmaple...
  • lot of API calls to get from stdout -> string
  • Haxe array doesn't have each without some static extension -- stringing together expressions isn't the norm. Instead more intermediate vars are created.
  • Haxe's String.split interprets trailing delimiter as (empty string on the right) while Ruby's doesn't. This adds a blank filename and makes Haxe require the file exists check (or a pop would do.) Well played, Ruby.
  • The dev cycle (make changes / leave editor / compile / test, repeat) was more clunky than the Ruby REPL.

@hughsando
Copy link
Member

I think most of these problems come from the process api - and maybe that is what makes a good scripting library. But maybe not. I think its just a library thing. Ideally you would do something like this:

var sum=0;
FileSystem.findMatching(srcDir, "*.hx", fn -> sum+= File.getContent(fn).split("x").length-1);
println(sum);

Which is only one utility function (findMatching) away from being 1:1 code match.
The other thing that makes it less scripty is FileSystem, File and println are not top-level, and the boilerplate code already mentioned, which I guess we are looking to help here.

On the plus side, the haxe will work on windows without cygwin, and this is why I use haxe for my scripting, and write the utility functions as required. eg https://github.com/haxenme/nme/blob/master/tools/make_classes/MakeClasses.hx#L7 is pretty similar.

@back2dos
Copy link
Member

Ok, I took a moment to think about it and I apologize for the length of my post. Then again I hope it contains all I have left to say on the matter ;)

First of all, "X is an interesting point but should be put in a different proposal" just doesn't work. Take the optionality of the package statement I brought up. You can consider that a different question, but there's no wisdom in doing so. The basic question is the same - "what is the guiding principle?" - and the possibility to omit the wrapping class or the package statement are part of the same answer. If you make that two questions and answer them differently, you will violate the principle of the least surprise. This is a no-go, especially if your declared goal is to make the language more palatable for newcomers.

Good design is driven by principles, not by features. We're headed the opposite way here. So look: if after 12 or so years we wish to reconsider/reformulate/realign Haxe's principles, I very much welcome that. But that's not at all what is happening.

Beyond the risk of inconsistency, there is also the risk of self-incapacitation. One can pretend that the idea to make Haxe "syntactically more suitable for scripting" is entirely separate. I don't see that. It is only a separate question once you can guarantee separation, which requires a concrete solution for having this idea and the other at the same time. That may be disambiguation per file extension or what not. But we need to explore this problem at least to the point of understanding how much the cost of adding the second will increase by adding the first and count that towards the price of adding the first. Language features do not exist in a vacuum. Some of my favorites:

  • we can't have unary postfix ? because ternary ? was such an existential time-saver that it had to be included (despite the fact that if/else does the same)
  • we can't have operator overloading through static extension, because it seemed so clever to apply static extensions across implicit casts

It is pure hubris to think that you can simply add things to a design because for the moment it seems cheap and you can't think of future problems down the road just yet. Perhaps a little retrospective by Tony Hoare makes that point better than I ever can:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Yes, of course you cannot predict all future problems. But you should really make an effort instead of simply resorting to "this is not part of the proposal". And even if you cannot think of anything, it is always more prudent to assume that adding complexity has an inherent cost, so if you can't make a strong case for the benefits (and I don't see it here) that passes a compelling threshold, the arguments simply do not weigh in your favor. The burden of proof should be shouldered by those defending the proposal, not those challenging it.

FWIW, the more I think about the proposal, the more I dislike the implementation details, because it's built on a pretty leaky abstraction. You have this implicit class and you need to take care that it doesn't pop up everywhere. It should not be in the className of PosInfos, it should not be in autocompletion, it should not be in the -xml output or dox will have to treat it specially, which leads to the broader question how haxe.rtti will actually treat it. Also: I would really want the ability to put a single function into a single module and name the module the same as the function (as is common practice with node modules, e.g. minimist), rather than having to invent a different name for the module for no reason other than the abstraction leaking out implementation details.

I think that can be remedied. My advise would be to support it properly instead of resorting to such hackery: have top level vars and functions in all of the type system (not just rtti and documentation and macros which also need to treat KModuleStatics) and let the generators deal with it. In most of the cases, there's a corresponding construct, so why the indirection? Yeah, it'll be a bit more work but if it's not worth going the extra mile than I really doubt it's worth adding in the first place.

As far as reflection is concerned, I would add that there should be something like Type.resolveGlobal to get a top-level function/variable.

@Pauan Look. My personal opinion is that Haxe's syntax is quite baroque and could benefit from conciseness. Here is what I do: I get over it. And yes, it's my genuine opinion that people who give such syntactic trivialities enough weight to pick one language vs another have no shortage of either of the concrete mental qualities I ascribed to them. Obsessing over syntax is a guilty pleasure that I sometimes also find hard to resist and while I sympathize I do not even in the slightest consider it reasonable and I don't see that I have to. Nor do I see that anything becomes better by long-winded posts about how writing a little less code is such a huge benefit (see the irony?). I called no-one a moron and I don't insult people who disagree with me. To make such claims is baseless (I don't disagree with your preference), slanderous and intellectually lazy. Please refrain from doing that and we'll be just fine. Thank you.

We share the same preference. That doesn't mean I will blindly accept flimsy, speculative or even quite fallacious arguments just because they support my point of view. If all that we're left with is "I like it better this way" then that's what we have and trying to conceal it behind a grand discourse is just dishonest. I'm sure you can do better than that and I invite you to do so ;)


I absolutely agree with Hugh that Haxe makes for a great scripting language and after overcoming some initial pain it has become my weapon of choice. The speed of Haxe's static analysis makes its cost negligible for scripting tasks and the runtime speed of eval is just fine (I'm pretty optimistic it beats Ruby for CPU-intensive stuff). Pursuing this is a worthwhile goal and would entail:

  1. Having a "scripting mode" without the boilerplate.
  2. Writing the utility functions that typical scripting tasks require and quite possibly autoimport them in "scripting mode".
  3. Properly advertising Haxe's strength as a scripting language.

With the rise of devops and automation, scripting is no longer just a matter of quickly hacking something together in an interactive shell that you fire and forget, but by growing robust and maintainable processes and I believe Haxe could meet the increasing demand really well. We'd have to want it and then to communicate it. I believe we should, because it is something that will be relevant to existing Haxers (some of whom still use gulp or maven or whatnot) and can truly drive further adoption.

The first step - and I cannot stress this enough - should therefore not be made harder by this proposal. With that provided, I don't care much about what happens to this, although I believe that a less hacky approach will pay off in the long run.

@Aurel300
Copy link
Member

Thanks for the post @back2dos .

I was originally mostly supportive of the proposal, but now I think the time "saved" is better spent by simply having e.g. a script which generates boilerplate code (an approach I take for my Haxe scripting), or an IDE which does the same.

As for utility functions which would be more "accessible" by being module-level, static extension is fine, or a short, one-letter module name if necessary.

As for the entire scripting tangent … While I do agree the standard Haxe API is lacking in some respects and inconsistent in many cases, this is a job for a specific library. It could provide simple functional programming for file glob patterns, directories, etc etc – whatever is generally used in scripting / batch automation.

@fullofcaffeine
Copy link

Any news here? 🙄

@nadako
Copy link
Member Author

nadako commented Sep 26, 2017

There's a bit of stalemate here right now. I still think that module-level functions would be a nice addition to the language without introducing a lot of new concepts to the user. At the same time, after what @back2dos said and what we discussed on Slack, I'm thinking that if we want to have module-level functions/vars, maybe we should indeed introduce them as proper type-system citizens instead of having a wrapper class auto-generated. But that's a lot more intrusive change wrt implementation, so I'm not sure if it's really worth it...

@hughsando
Copy link
Member

No point over-complicating the implementation. From the c++ backend point of view, the auto-gen class would work well. Any concerns w.r.t. reflection can be unit tested relatively easily and implemented gradually, which makes things easier. If you can't tell from the outside what implementation is used, then you can't care about it.

@nadako
Copy link
Member Author

nadako commented Oct 11, 2017

@hughsando I think the concern here is about macro representation mostly.

Anyway there are still three options and I'm personally fine with either:

  • type module-levels in a primary module class, thus forbidding explicit primary module class - probably the simpliest approach
  • create a special class for module-levels and allow primary module class - some more work needed wrt resolution rules
  • have module-levels as a first-class citizen (so a new variant to module_type/ModuleType) - the most intrusive, but probably the most correct implementation

@hughsando
Copy link
Member

Yeah, the macro thing requires some thought.
There could always be a default implementation that bundles the module items into a default class immediately prior to the backend, so you don't need to change the backends, no matter what the internal representation is.

@ncannasse
Copy link
Member

ncannasse commented Oct 11, 2017 via email

@fullofcaffeine
Copy link

So do we all agree on how to proceed here? I just don't want the PR to be open for another year, which pretty much kills any momentum this proposal (still) has :)

@nadako
Copy link
Member Author

nadako commented Oct 20, 2017

open for another year

Hey this PR is only a couple months old :)

Anyway, I'll look into actual implementation.

@fullofcaffeine
Copy link

@nadako Almost half a year :)

Anyway, no pressure whatsoever, we're an open-source community after all and it's all voluntary work. I just pinged to know the status. Btw, shouldn't we merge this? We got enough votes from core-team members no?

Cheers!

@nadako
Copy link
Member Author

nadako commented Nov 14, 2017

Most people like the idea, while the actual implementation regarding macro structures is still kind of in question, yet it's described in the proposal, so I'm unsure about merging it in its current state.

@elsassph
Copy link

I missed the proposal but I want it! I'm happy with an implicit module class.

Please make it happen!

@jgranick
Copy link

jgranick commented May 31, 2018

Could we allow lowercase filenames? openfl.net.navigateToURL could be a reality on Haxe? 😓

Perhaps lowercase filenames could be functional, and uppercase could be modular (though I'm generally in favor of no special magic based on file cases)

BTW, this could be helpful for HXP-style replacements of HXML

build.hx

args = [ "-neko", "out.n", "Main" ];
#if debug
args.push ("-debug");
#end
Sys.command ("haxe", args);

@ghost
Copy link

ghost commented Jun 8, 2018

Is this on the roadmap for haxe 4? IMO it would be a great addition.

@fullofcaffeine
Copy link

Will this ever be accepted and merged? 🤔

@nadako
Copy link
Member Author

nadako commented Jul 2, 2018

Will this ever be accepted and merged? 🤔

I think so, but probably not for Haxe 4.0

@Simn
Copy link
Member

Simn commented Sep 21, 2018

HaxeFoundation/haxe#7452

@Simn Simn merged commit 05c77ae into HaxeFoundation:master Sep 21, 2018
@nadako nadako deleted the module_level_funcs branch June 20, 2019 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet