GithubHelp home page GithubHelp logo

ahnfelt / funk Goto Github PK

View Code? Open in Web Editor NEW
53.0 53.0 2.0 27 KB

PROTOTYPE: A minimal scripting language - OOP via lambda functions and pattern matching

JavaScript 86.90% HTML 13.10%
functional-programming minimal-scripting-language programming-language

funk's People

Contributors

ahnfelt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

funk's Issues

Sending messages to self

Currently the only way I found to send messages to self is like this:

:newDog {| barkPhrase | {
    | Bark | system.Log(barkPhrase)
}}

:newChiwawa {
    :parent newDog("yip!")
    :self {
        | Yap | self.Bark
        | m | parent(m)
    }
    self
}

:aDog newDog("woof!")

:myChiwawa newChiwawa()

aDog.Bark
myChiwawa.Yap

Should Funk define a "self_" function on the prelude?

Type System

Here are some thoughts on a type system for Funk—I just took the examples from the slides and wrote some ideas about what their inferred types ought to be (and why). I’m just focusing on static types for now; incorporating dynamic/gradual typing would be a good next step after these details are figured out.

{|x y| x + y} : forall a b r. { "+" : a -> b | r } -> a -> b

y is inferred as a fully polymorphic type a; x is also polymorphic, but constrained to accept a message + which takes an argument of type a and returns a result of type b. The lambda just passes this message along to x, so it also returns a b.

The row polymorphism here (forall r. … { … | r } …) indicates a record that may contain more fields (resp. an object that may accept more messages) than those that are explicitly indicated. This allows for a kind of subtyping: a “larger” type that accepts more messages than just + can be passed for the x parameter. In following examples, I’ll make this polymorphism implicit.

{|x| x + 1} : forall a. { "+" : number -> a } -> a

Here, x is passed the message + with an argument of type number, so its type is constrained accordingly. The result type remains unconstrained. A more concrete type such as number can be passed for the x parameter, in which case the inferred type of this lambda would be constrained further, to number -> number. I use lowercase number instead of Number to differentiate it from Funk’s treatment of uppercase barewords as string symbols, for reasons that will be clear in a moment. It could also be called num or something if you prefer.

This type is also a generic instance of the more concrete type number -> number, and the user might supply that as a type annotation.

{1} : forall a. a -> number

This function does nothing with its argument, so its input type is unconstrained, and it returns a number.

fib := {
  |0| 0
  |1| 1
  |n| fib(n - 1) + fib(n - 2)
} : number -> number

The patterns in fib constrain its input to be of type number; its results, 0 and 1, also constrain its result to number. This could be given a more general type if number literals were polymorphic (as in Haskell) but I’m skipping over that for the sake of simplicity.

color := {
  |Red| "#ff0000"
  |Green| "#00ff00"
  |Blue| "#0000ff"
} : { Red : string, Green : string, Blue : string }
; : (Red -> string) /\ (Green -> string) /\ (Blue -> string)
; : (Red \/ Green \/ Blue) -> string
; : colorName -> string

Here there are a few different options. If Red, Green, and Blue are taken as symbols with singleton types (that is, the type of the value Red is the type Red), then color is inferred to have an “object” type which indicates the result types of the messages it responds to—here, all String. This is essentially an intersection type (Red -> string) /\ (Green -> string) /\ (Blue -> string), denoting an “overloaded” function. (The /\ bikeshed could also be painted & if you prefer.) Because the result types are all the same (string), this can be simplified into a function that takes a union type: (Red \/ Green \/ Blue) -> string or (Red | Green | Blue) -> string. If the type Red \/ Green \/ Blue is known by some other name such as colorName, then it could be further simplified to colorName -> string.

if := {
  |True t _| t()
  |False _ f| f()
} : forall a. { True : forall b. (null -> a) -> b -> a, False : forall c. c -> (null -> a) -> a }
; : forall a. (forall b. True -> (null -> a) -> b -> a) /\ (forall c. False -> c -> (null -> a) -> a)
; : forall a. (True -> (null -> a) -> (null -> a) -> a) /\ (False -> (null -> a) -> (null -> a) -> a)
; : forall a. (True \/ False) -> (null -> a) -> (null -> a) -> a
; : forall a. boolean -> (null -> a) -> (null -> a) -> a

if is similar to color, in that we can infer its type based on the messages it accepts, then simplify it to a more readable type. In the most general case, I think the unused branches could be given fully polymorphic types, but that would leave you with higher-rank quantifiers, which might be a pain to deal with. So instead, the intersection of the two function types can be simplified by unification, making both branches null -> a in both the True and False cases. Then the intersection can be simplified into the union True \/ False, a.k.a. boolean (or bool).

max := {|x y|
  if (x > y) {
    x
  } {
    y
  }
} : forall a. { ">" : a -> boolean } -> a -> boolean

This should be self-explanatory based on previous examples.

when := {
  |True body| body()
  |False _|
} : { True : forall a. (null -> a) -> a, False : forall b. b -> null }
; : (forall a. True -> (null -> a) -> a) /\ (forall b. False -> b -> null }
; : (True -> (null -> null) -> null) /\ (False -> (null -> null) -> null }
; : (True \/ False) -> (null -> null) -> null
; : boolean -> (null -> null) -> null

Because the False branch does nothing, its argument’s type is unconstrained, and (I presume) it returns null. So when the intersection is simplified, the a from the True branch is unified with null, and the b from the False branch is unified with null -> a, giving the type boolean -> (null -> null) -> null.

while := {|condition body|
  when (condition()) {
    body()
    while(condition, body)
  }
} : forall a. (null -> boolean) -> (null -> a) -> null

Here the type of the body’s result is left unconstrained, since it’s never used. The return type of while is inferred as null because that’s the return type of when. (As an aside, for optimisation purposes, the compiler needs to recognise that the call to while is in tail position.)

Now we get into the interesting stuff: objects!

vector := {|x y| {
  |X| x
  |Y| y
  |Add v| vector(x + v.X, y + v.Y)
}}

The inferred type for this would be quite gnarly if allowed to be fully general. This is the best I could come up with:

forall a b addA addB.
  ; The types “a” and “b” of the fields must accept “+” messages…
  (a <: { "+" : addA -> a }, b <: { "+" : addB -> b })
  => mu self. a -> b
  ; …that can accept the types of the fields “X” and “Y” of the value passed to “Add”.
  -> { X : a, Y : b, Add : { X : addA, Y : addB } -> self }

This can’t easily be simplified, so a user-supplied type annotation would be needed in situations like this to produce a readable type:

number -> number -> mu self. { X : number, Y : number, Add : { X : number, Y : number } -> self }

The notation for recursive types could be spelled differently, too, like rec self; or self could implicitly refer to the nearest containing {…} type. And type aliases could be introduced for convenience:

type vector = mu self. {
  X : number  ; NB. newline could be accepted where comma is expected
  Y : number
  Add : { X : number, Y : number } -> self
}

newVector : number -> number -> vector

Record union captures the notion of forwarding messages in the inheritance example:

type monster := {
  Name : string
  Hurt : number -> null
}

newMonster := {|name hitpoints|
  |Name| name
  |Hurt damage| hitpoints -= damage
}} : string -> number -> monster

type creeper := { Explode : number -> null } + monster

newCreeper := {
  super := newMonster("Creeper", 80)
  {
    |Explode area|
      area.NearbyMonsters.Each {|monster|
        monster.Hurt(50)
      }
    |otherMethod|
      super(otherMethod)
  }
} : null -> creeper

The “sum types” example works out pretty neatly too:

getOrElse := {|option default|
  option {
    |None| default
    |Some value| value
  }
} : forall a. ({ None : a, Some : a -> a } -> a) -> a -> a

You could always add “real” sum & product types if you want, rather than encoding them like this—it might be better for producing good error messages and checking whether pattern matching is exhaustive.

Extensible Records with Scoped Labels is a good starting point for implementing extensible records—it’s the basis of the record system in PureScript and the effect system in Kitten. If you want to incorporate higher-rank types, lexi-lambda did a reference implementation of Complete and Easy Bidirectional Typechecking for Higher-rank Polymorphism; even if you don’t need higher-rank polymorphism, bidirectional typechecking is a good technique that tends to produce better error messages than vanilla Hindley–Milner.

That’s all I’ve got for now—let me know your thoughts. :)

Delegation raises "Unexpected argument"

Really liked the concept. It's quite close to something that has been bouncing on my head for quite some time (a decade :P)

I think I found an issue that I don't have the time right now to check.

Trying:

:a {
  :b 1
  {
      |Add n| n + b
  }
}

:c a()

system.SetText(c.Add(2))

Results on:

Unexpected argument: function(_X) { if(_X == "Add") { return function(_X) { var n_ = _X; return _A(_A(n_, "+"), b_); }; } throw "Unexpected argument: " + _X;}

While:


:a {
  {
      |Add n| n + 1
  }
}

:c a()

system.SetText(c.Add(2))

Works.

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.