Class: Heist::Runtime::Function

Inherits:
Object
  • Object
show all
Defined in:
lib/heist/runtime/callable/function.rb

Overview

There are several types of callable object in Heist: Function, Syntax, Macro and Continuation. For convenience of type detection, Function is treated as a the base class for all these, though they all typically override Function‘s core methods. The intention is to treat all these types as uniformly as possible, though this does introduce some API problems which still need resolving. In any case, the current state of things:

            ============
            | Function |
            ============
                 |
     ---------------------------
     |                         |
==========              ================
| Syntax |              | Continuation |
==========              ================
     |
 =========
 | Macro |
 =========

Heist is designed to be reasonably modular in that none of Scheme’s special forms or built-in functions are mentioned in the parser; instead they are all implemented as first-class functions and stored in the global scope when you initialize a Heist Runtime. The Heist runtime does implement a fair amount of Scheme-specific functionality but you’re not forced to use any of it: at its core it is designed as a general processor for s-expressions and could be used as a backend for any Lisp-style language. The only hard-and-fast rule is that, to evaluate an expression, you evaluate its first element, and call the resulting function with the remaining elements.

The Heist callable types – Function, Syntax, Macro and Continuation – differ primarily in terms of what they do with their arguments when called. All are passed the current Scope and a Cons list containing the rest of the current expression when they are called; it is up to the type of function being called what to do with the expression.

The Function class is used for representing the basic idea of a Lisp procedure; a reusable block of code that can accept zero, one or more arguments, return output values and cause side effects on the state of the running program. Heist functions can be implemented using Ruby blocks or Scheme lists.

For information on other callable types, see Syntax, Macro and Continuation.

Direct Known Subclasses

Continuation, Syntax

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scope, formals = nil, body = nil, &block) ⇒ Function

A Function can be initialized in one of two ways. Both types require a Scope as the first parameter: this is the lexical scope in which the Function is defined. The other parameters should consist either of a Ruby block that accepts the procedure’s arguments and returns its result:

env = Scope.new
env['plus'] = Function.new(env) { |a,b| a + b }

Or, the parameters should be a list of formal argument names for the procedure, and a list containing zero or more Scheme expressions that make up the procedure’s body. If the list of formals is an improper list, the tail name will be assigned a list of any parameters remaining after all the preceeding formals have been assigned values. If the list of formals is not a list but a single identifier, that identifier will be assigned a list of all the parameters when the procedure is called.



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/heist/runtime/callable/function.rb', line 74

def initialize(scope, formals = nil, body = nil, &block)
  @formals, @rest = [], formals
  if Cons === formals
    tail     = formals.tail.cdr
    @formals = formals.map { |id| id.to_s }
    @rest    = (Identifier === tail) ? tail : nil
  end
  @scope = scope
  @body  = body || block
  @lazy  = scope.runtime.lazy?
  @eager = !scope.runtime.stackless?
end

Instance Attribute Details

#bodyObject (readonly)

Returns the value of attribute body.



54
55
56
# File 'lib/heist/runtime/callable/function.rb', line 54

def body
  @body
end

#nameObject

Returns the value of attribute name.



54
55
56
# File 'lib/heist/runtime/callable/function.rb', line 54

def name
  @name
end

Instance Method Details

#apply(params) ⇒ Object

Returns the result of applying the Function to the given collection of params. If the procedure is primitive, this will return a value immediately. If it’s a Scheme function, this simply binds the params to the procedure’s formal arguments and returns a Body with the procedure’s body expressions and the newly allocated Scope. We use a trampoline to call Functions iteratively to allow tail call optimisation. (See Stackless, Stack, Frame and Body for more information.)



119
120
121
122
123
124
125
126
127
128
129
# File 'lib/heist/runtime/callable/function.rb', line 119

def apply(params)
  return @body.call(*params) if primitive?
  closure = Scope.new(@scope)
  idx = 0
  @formals.each do |name|
    closure[name] = params[idx]
    idx += 1
  end
  closure[@rest] = Cons.construct(params[idx..-1]) if @rest
  Body.new(@body, closure)
end

#call(scope, cells) ⇒ Object

Calls the Function with a calling Scope (in which to evaluate the parameters), and a Cons list containing the parameter expressions. In lazy mode, the parameter expressions are wrapped in Binding objects for later execution. If using the continuation-supporting Stack interpreter (as indicated by the @eager flag), the parameters will already have been evaluated and should not be re-evaluated lest we try to interpret data lists as code. Returns the result of calling apply with the evaluated parameters.



103
104
105
106
107
108
109
# File 'lib/heist/runtime/callable/function.rb', line 103

def call(scope, cells)
  params = cells.map do |arg|
    lazy? ? Binding.new(arg, scope) :
            (@eager ? arg : Heist.evaluate(arg, scope))
  end
  apply(params)
end

#lazy?Boolean

Returns true iff the Function is lazy, i.e. it does not evaluate its arguments unless it needs their values. Only non- primitive functions can be lazy since we have no way of telling when Ruby needs to access a value. Besides, primitive functions will typically use all their parameters.

Returns:

  • (Boolean)


142
143
144
# File 'lib/heist/runtime/callable/function.rb', line 142

def lazy?
  @lazy and not primitive?
end

#primitive?Boolean

Returns true iff the Function is a primitive, that is to say it is implemented in Ruby rather than Scheme.

Returns:

  • (Boolean)


133
134
135
# File 'lib/heist/runtime/callable/function.rb', line 133

def primitive?
  Proc === @body
end

#to_sObject Also known as: inspect

Returns a string placeholder for the Function, containing its name if it has one.



148
149
150
# File 'lib/heist/runtime/callable/function.rb', line 148

def to_s
  "#<procedure:#{ @name }>"
end