sanctuary-js / sanctuary-def

Run-time type system for JavaScript
MIT License
294 stars 23 forks source link

Defining functions that take no arguments or multiple at once #180

Open Avaq opened 6 years ago

Avaq commented 6 years ago

Consider this function:

f ('a', 'b') ('c') ()

Currently def cannot express its type. I would like to explore changes to its API to allow for the definition of any sort of JavaScript function (except methods).


More lists

-def :: String -> StrMap (Array TypeClass) -> NonEmpty (Array Type) -> AnyFunction -> AnyFunction
+def :: String -> StrMap (Array TypeClass) -> NonEmpty (Array (Array Type)) -> Type -> AnyFunction -> AnyFunction

The type could be encoded as a list of lists of Types specifying function input, and a single Type argument specifying output (we have to move it to a separate argument to keep the types consistent). So our f function could be defined as such:

def ('f')
    ({})
    ([[a, b], [c], []]) // input
    (d)                 // output
    (body)

We can encode "multiple arguments at once" and "no arguments" by using the second level of lists. I like this solution, because the input specification mirrors function application exactly:

('f') ({}) ([[a, b], [c], []])
  f          (a, b)  (c)  ()

If we would go for this solution, my preference would be to make a change to the $Function-api to use the same approach to specifying its type. But this is not required.


Taking a function definition as input

-def :: String -> StrMap (Array TypeClass) -> NonEmpty (Array Type) -> AnyFunction -> AnyFunction
+def :: String -> StrMap (Array TypeClass) -> Type -> AnyFunction -> AnyFunction

The Type there would have to be a Function type as defined by $Function. Since $Function is already capable of expressing complex function types by means of nesting, this would be sufficient to define f:

def ('f')
    ({})
    ($Function ([a, b, $Function([c, $Function([d])])]))
    (body)

Additional thoughts

We won't necessarily have to change the def API. We can implement def on top of, say, defn as a variant optimised for pure curried functions:

defn ("K") ({}) ([[a], [b]]) (a) //or:
defn ("K") ({}) ($Function ([a, $Function([b, a])]))

def  ("K") ({}) ([a, b, a])
Avaq commented 6 years ago

We also briefly discussed (offline) the possibility of unifying the def API and the $Function API. I'm writing it down just to remember. The idea being that if we move the constraints argument to after the argument list, we could almost say that:

const $Function = def ('(anonymous)')

And $Function (args) would return a function still waiting for its type variable constraints and implementation, which could in turn be provided by the outer function at calltime.

davidchambers commented 6 years ago

There are several exciting ideas here. I'm not yet sure what we should do, but it doesn't seem right for $.Function to be able to describe function types which def itself cannot. I :heart: the idea of unifying these functions, but the fact that they relate differently to type variables may prevent this.

davidchambers commented 4 years ago

Are you still interested in this functionality, @Avaq?

Avaq commented 4 years ago

There's a lot of functionality, including this, I would like to have in order to achieve the broader goal of maturing sanctuary-def as a domain modelling tool. But because the scope of work to achieve this is so large, I tend to focus my attention elsewhere.