Giter VIP home page Giter VIP logo

Comments (7)

j14159 avatar j14159 commented on May 20, 2024 1

I'm quite reluctant to remove differing arity definitions for the same named function since it's common enough in the Erlang world that removing it might be a stumbling block for those coming from there but autocurrying makes enough sense to make it happen. My primary concern is bounding ambiguity - your proposal to simply drop out with a type error when we don't have enough information to make a decision seems pretty reasonable to me provided we make things clear enough for a user.

So the following would generate an error along the lines of {error, {ambiguous_application, foo/1, foo/2}} - but maybe less hostile than that :)

foo x = x + x

foo x y = x + y

make_an_error () = 1 |> foo

While the following would not:

foo x = x + x

foo x y z = x + y + z

-- unit -> (int -> int)
passes_typing () = 2 |> foo 1

Thoughts? Am I clinging too tightly to the differing arity thing?

from alpaca.

danabr avatar danabr commented on May 20, 2024

I think the first arity error makes sense. What if there was also a curried/2 and curried/3? In both Erlang and MLFE functions with the same name but different arity are considered different functions. The call curried 1 2 3 is thus ambiguous.

I don't know why the parenthesized version does not work. Spelling it out works:

test_curried () =
  let f1 = (curried 1) in
  let f2 = f1 2 in
  f2 3

The exact error ((curried 1) 2) 3 fails with is:

   {error,
    {invalid_fun_application,
     {error,
      {invalid_fun_application,
       {mlfe_apply,undefined,{symbol,13,"curried"},[{int,13,1}]},
       [{int,13,2}]}},
     [{int,13,3}]}}],

from alpaca.

lepoetemaudit avatar lepoetemaudit commented on May 20, 2024

Indeed, I'd mistakenly thought that only one function (regardless of arity) was allowed per name per module, therefore auto-currying would be possible, but I see that this was an oversight that is being corrected.

I agree with @danabr that multiple arity functions and auto-currying do not mix well. Scala has a special syntax for curried functions: http://docs.scala-lang.org/tutorials/tour/currying.html but I think that would be unnecessarily complicated. I guess my concern is that people familiar with other ML languages may be surprised by the lack of automatic currying, and things like point-free style are difficult to implement without it.

I suppose the typer could allow automatic currying if one and precisely one variant only of a function were defined, and throw an appropriate error in the case that currying would lead to ambiguous use. I guess the sweet spot is how to be a nice Erlang citizen while being as nice an ML as possible :)

The problem with not being able to apply the result of functions stems from here:

https://github.com/alpaca-lang/alpaca/blob/master/src/alpaca_parser.yrl#L404

As far as I understand it there's no rule to match applying on a general expression (specifically here, an apply expression). I wrote a test for the case and managed to write a rule in the parser, but I think that the alpaca_apply record will need an extra field that can take a general expression as the function instead of just Name as it has it currently. (theoretically this would also allow anonymous functions in future).

from alpaca.

j14159 avatar j14159 commented on May 20, 2024

I'm completely open to a discussion about multiple arity versions vs automatic currying, I just stuck that "fix" in today because it felt a little closer to Erlang and I was writing some Alpaca that could take advantage of it :) If it makes sense to back that out in favour of auto-currying I don't really have a problem with it tbh. Would be curious to hear arguments for/against.

As for the AST changes I think we're going to end up wrapping the returns in core-erlang let-bindings in the code generation stage as the following:

-module(test).

curried(X) ->
    fun(Y) -> X + Y end.

apply_curried() ->
    (curried(2))(3).

becomes this in core Erlang (some output redacted for brevity):

'curried'/1 =
    %% Line 3
    fun (_cor0) ->
        %% Line 4
	( fun (_cor1) ->
          call 'erlang':'+'
    	  (_cor0, _cor1)
	  -| [{'id',{0,0,'-curried/1-fun-0-'}}] )
'apply_curried'/0 =
    %% Line 6
    fun () ->
    let <_cor0> =
    	%% Line 7
        apply 'curried'/1
            (2)
        in  %% Line 7
            apply _cor0
                (3)

In this case should we have a different AST node for application of expressions or replace name with the expectation of a name or an expression? Maybe the latter is best, it's going to result in a branch in the code generation step regardless.

from alpaca.

lepoetemaudit avatar lepoetemaudit commented on May 20, 2024

I like the latter approach - the AST node would have a 'function' part which is either a name of a function or an expression which yields a function.

I'll present my argument here for my preferences on autocurrying.

I'm very much in favour of autocurrying. SML, Miranda, Haskell, Elm and OCaml all implement it, and it makes the language very malleable. I propose we allow autocurrying and multiple arity functions, but if more than one variant of a function is defined and not enough arguments are provided to match any variant, the typer errors with an error message explaining that autocurrying is disallowed in these circumstances (and suggest renaming the variants).

If push came to shove, I'd personally prefer autocurrying to multiple arity. I don't think the loss is too great - you just have to give functions different names - but you gain a great deal. One example is that a function like |> otherwise only works on functions which take one argument but with autocurrying it works with more, as in:

-- Imaginary function filter == (a -> bool) -> list a -> list a
let filtered = 
  [1, 2, 2, 3, 3, 3] |> filter ((==) 2)

-- result: filtered == [2, 2]

...so this imaginary filter function takes a predicate function that returns a bool, a list of 'a and returns a list of 'a. We call filter with only one argument, so we get a function that is 'waiting' for a list. |> applies the value on the left to the function on the right, which is now the curried form that only takes one argument, and everything works out :) I snook another example in there too - ((==) 2) being a prefix invocation of ==, meaning we get a curried function that takes a single argument that will return true if the argument is 2. The level of expressivity you can achieve with a really small toolbox is incredible, and people coming from Elm, OCaml etc., will feel right at home with it.

Counter-arguments for me are - we're in Erlang land and multiple arity is a natural fit there. Also we can emulate autocurrying above by providing a standard library with 'pre-curried' functions with different arities and functions for currying, and at worst we can always return / wrap using anonymous functions, once those are implemented. It's also possible to get carried away with techniques like point free style which can lead to terse and hard to read code.

from alpaca.

lepoetemaudit avatar lepoetemaudit commented on May 20, 2024

I think that looks great. I'm sure if it ever does get confusing there will be ways of disambiguating, syntactically if needs be.

from alpaca.

j14159 avatar j14159 commented on May 20, 2024

I've opened issue #74 to address the automatic currying piece of this so we can fix the basic expression application problem on this one.

from alpaca.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.