Examples

Functions

A summary of some of the features of functions in CellSpeak:

  • functions can have multiple return values
  • functions can be constant or variable functions
  • there is a short form of a function definition
  • functions can capture data in their scope at definition
  • functions, constant or variable, can be used as parameters
  • functions can be standalone, part of a design or part of a type (methods)
  • functions can be nested
  • functions can be used to define operators









(1) If you want to give names to multiple return values, then the total return spec must be put between brackets. Without names you can also write:  function Divide( int a, int b ) out float, ansi is  In any case the names are for information only - no variables are declared.












(2) The compiler selects the most efficient way of passing parameters - by value or by reference. Arrays and records are always passed by reference, irrespective whether they can be changed or not. The compiler will check that a parameter that is an in parameter, is not modified in the function.





(3) We can define a record inside a record - makes for a compact definition where we do not have to define types for everything.




















(4) This is how we initialize the sub-record that we have defined as part of the record.

















(5) This is a short function - will be inlined by the compiler.































(6) The name of a variable function - like any variable - must be unique, that is why we only need the name to refer to the function variable. Being able to change the body of a function can make code both more readable and faster at execution. For example, depending on some initial checks and conditions, different versions of a function can be plugged in, thus avoiding that checks need to repeated at every invocation of the function.

























(7) The withpart follows after the function header. It contains a list of data that is permanent in the function, i.e. that is remains intact between function invocations. The space for that data is released automatically when the function goes out fo scope.






(8) When there is only a keep part in the data section- capturing variables that are in scope - then the with .. end can be omitted.



















(9) When a function is not defined immediately, then the declaration has to be followed by to do. A function definition like  function ConicSection( float x, float y) out float to do  can be used as the template for other function definitions e.g.  function Ellipse like ConicSection is ... . This is how function types are implemented in CellSpeak.

(10) The function IFlyToo gets its own copy of the local data of Fly. With a simple assignment, both functions would share the same data.














(11) The function f is a parameter. In a function definition as a parameter, the return values have to be enclosed in parentheses to avoid ambiguity with the rest of the parameter spec.

05 Functions.celsrc
use Windows, Math, Strings, Editor

group Functions

design Demonstrator is
 
	-- We use a cell as the output sink
	cell Window

	-- again we do all our demo's in the constructor
	constructor is
	
		-- We create the output cell.
		Window = create MenuWindow("Function Demonstrator")
	
	-. 1. The basics .-
	
		Window <- print("\n\n*** Test1: the basics\n")
	
		-- A trivial function
		function Add( int a, int b ) out int is			
			return a + b			
		end
		
		-- We can give a name to the output as well - the name is informative only and not used in the function
		function Subtract( int a, int b ) out ( int Result ) is		
			return a - b		
		end
		
		-- We can define single-expression functions as follows - the result of the expression is the implicit return value
		function Multiply(int a, int b) => a * b
	
		-- Suppose we want to return a possible error condition, we can define multiple outputs
		-- The name of the output parameters is for information only
(1)		function Divide( int a, int b ) out ( float Result, ansi Error ) is	
			if b is 0 then 
				return inf_f, "Division by zero !")
			else 
				return a/b, null
			end
		end
		
		var Result, Error = Divide( 56,0)
		-- Note that the \"\" in the string is just an empty string
		Window<-print("\nThe function call returns: [Error ? Error : \"\" ] [Result]")
		
		Result, Error = Divide( 56, 7)
		Window<-print("\nThe function call returns: [Error ? Error : \"\" ] [Result]")
		
		-- Functions can be called recursively
		function Factorial( int n) out int => n ? n*Factorial(n-1) : 1		
		Window <- print("\nFactorial(7) returns = [Factorial(7)]")
		
	-. 2. Parameters and return values .-
	
(2)		-- Parameters to functions are 'in' parameters, i.e. they are not modified by the function.
		-- functions can have several return values, but sometimes it is also necessary to be able to have an 'out' parameters
		-- i.e. parameters that the called function can change
		-- Note that parameters will always be passed as efficiently as possible, irrespective whether thay are 'in' or 'out' parameters.

		Window <- print("\n\n*** Test2: In and out parameters\n")
		
		-- We define a record to hold some personal data ..
		type PersonRecord is record
			ansi 			Name
(3)			record 
				int 	Day
				ansi 	Month
				int 	Year
			end 			Birthday
			ansi 			SoundAdvice
			ansi 			ZodiacSign
		end
		
		-- and define a function to calculate some key numbers ...
		function Numerology(out PersonRecord Person, int A, int B) out (int LifePathNumber, float HeartsDesireNumber) is
		
			-- Set the name in upper case (for no particular reason)
			Person.Name.upper()		
			
			-- get the zodiac sign .. you get the drift
			if Person.Birthday.Month is "january" and Person.Birthday.Day >= 20 then 
				Person.ZodiacSign = "aquarius"
			else
				Person.ZodiacSign = "squid"
			end		
			
			-- set the advice field
			Person.SoundAdvice = "Watch out for venomous snakes on the [Person.Birthday.Day] day of each month")		
			
			-- and return two numeric values calculated with centuries old secret formulas..
			return Person.Birthday.Year % Person.Birthday.Day , (A^2 + B^2 - A*B)			
			
		end
		
		-- let's try this out on me ..
		PersonRecord Me = [
			"Sam Verstraete",
(4)			[23,"january",1958],
			null,
			null]
			
		-- call the function (the type of the variables is deduced from the function return types)
		var LifePathNumber, HeartsDesireNumber = Numerology(Me, 5, 4)
		
		-- and print the results
		Window <- print("
			[Me.Name], these are your results:
			Your astrological sign is [Me.ZodiacSign]
			Your Life Path Number is [LifePathNumber]
			Your Heart's Desire Number is [HeartsDesireNumber]
			And always follow this sound advice : [Me.SoundAdvice]")
			
	-. 3. Nested functions .-

		Window <- print("\n\n*** Test3: Nested functions\n")
	
		-- Functions can be nested in other functions.
		-- Nested functions have limited scope and have no access to variables in functions they are embedded in.
		-- Capturing variables that are in scope when the function is defined (closures - see below) is however possible.
		
		-- Example : a function to calculate the roots of a quadratic equation
		function QuadraticRoots( float A, float B, float C ) out (complex Root1, complex Root2) is
			
			-- we define a local function to calculate the discriminant
(5)			function Discriminant( float A, float B, float C ) => B^2 - 4*A*C
		
			-- calculate the discriminant
			var D = Discriminant(A,B,C)
			
			-- Two helpful quantities
			var I = sqrt(abs(D)) / (2*A)
			var R = -B / (2*A)
			
			-- Return the results according the sign of the discriminant
			if D > 0 then
				return [ R+I, 0.0], [R-I, 0.0]
			elif D < 0 then
				return [R, I], [R, -I]
			else
				return [R, 0], [R, 0]
			end	
		
		end
				
		-- Let's calculate a few roots (2x2x2 = 8 equations in total)
		for each A in [2.0, -2.0] do
			for each B in [4.0, -4.0] do
				for each C in [8.0, -8.0] do
					var R1, R2 = QuadraticRoots( A, B, C)
					Window <- print("\nThe roots of the equation y = [A,%2.1f].x^2 [B,%+2.1f].x [C,%+2.1f] are ([R1.r], [R1.i]) and ([R2.r], [R2.i])")
				end
			end 
		end
		
	-. 5. Function variables .-
	
		Window <- print("\n\n*** Test5: Function variables\n")

		-- Normal functions are constants - once defined they cannot change.
		-- Functions can however be defined as variables and can then be modified.
		-- To define a function as a variable, the declaration is preceeded by 'var'
		
		-- we can declare a variable and the function code for the variable in one declaration ..
		var function F1( xyz V1, xyz V2) out float is
			return V1*V2
		end

		xyz V1=[1,2,3], V2=[4,7,2]	
		Window<-print("\nResult for F1(V1, V2) is [F1(V1, V2)]")
				
		-- ..or specify/change the body later by using the body statement
(6)		body F1 is
			return (V1#V2).length()
		end

		Window <- print("\nChanged the body of F1, result for F1(V1, V2) is now [F1(V1, V2)]")
		
		-- We can define another constant function
		function F2( xyz U1, xyz U2 ) out float is 
			return (U1 + U2).length()
		end
		
		Window <- print("\nF2 is a new constant function. Result for F2(V1, V2) is now [F2(V1, V2)]")
		
		-- and assign that to the function variable
		F1 = F2
		
		Window <- print("\nResult for F1(V1, V2) after F1 = F2 is also [F1(V1, V2)]")
		
		-- Or we can define another variable function
		var function F3( xyz U1, xyz U2 ) out float is 
			return (U1 - U2).length()
		end		
		
		Window <- print("\nF3 is a new variable fucntion. Result for F3(V1, V2) is now [F3(V1, V2)]")
		
		-- and assign that to the function variable
		F1 = F3
		
		Window <- print("\nResult for F1(V1, V2) after F1 = F3 is also [F1(V1, V2)]")
		
	-. 5. Closures and function data fields .-
	
		Window <- print("\n\n*** Test5: Closures and function fields\n")

		-- 5.1 Defining function fields / closures
		
		-- some data that will be used in the functions below
		int ListPrice = 300
		ansi Airplane = "Boeing 747"

		-- Functions that are not fields of a record can have data fields between 'with .. end'
		function Fly(ansi Destination) 
(7)		with 
			keep Airplane, Window		-- keep copies variable in scope
			ansi Airline = "KLM"
			ansi From = "Amsterdam"
		end 
		is
			Destination is "Chicago" ? Airline = "Delta Airlines"
			Window <- print("\nI fly from [From] to [Destination] with a [Airplane] from [Airline].")
			From = Destination
		end
	
		-- If you just want to keep some variables in scope you can also simply use keep (no with .. end required)
		-- Note that the copy of Airplane in this function belongs to this function, and does not change when the original variable changes,
		-- or when other copies in other functions change.
		function Buy(ansi Seller)
(8)			keep Airplane, ListPrice, Window
		is
			Window <- print("\nI bought a [Airplane] from [Seller] for about [ListPrice] MUSD.")
		end	
				
		-- A function with data is called like a normal function
		Fly("Berlin")
		
		-- We define a function variable with the same signature
		var function AirplaneAction(ansi What) to do 
		
		-- Set it to a previously defined function
		AirplaneAction = Fly
		
		-- Calling a function variable is the same as calling a function
		AirplaneAction("New York")
		
		-- We can also assign a different function
		AirplaneAction = Buy
		
		-- ..and call the function again
		AirplaneAction("my neighbour")
		
		-- 5.2 Allocating function variables
		
		-- The data that is defined for a function belongs to that function. Even if 
		-- you assign the function to a variable function the data stays the same.
		-- Sometimes a function variable will needs its own copy of the data.
		-- This can be done by using the keyword 'new'
		
		Window <- print("\n\nFunctions with a copy of the captured data\n")
		
		-- We define a function variable
(9)		var function IFlyToo(ansi Where) to do
	
		-- IFlyToo will allocate and copy the data fields from Fly in this way
(10)	new IFlyToo = Fly
				
		-- The two functions will now start from new york - the current setting for Fly - 
		-- but follow different routes as each has a copy of the function data fields

		Fly("Chicago")
		Fly("Tokio")	
		
		IFlyToo("Paris")
		IFlyToo("Bejing")
		
	-. 6. Functions as parameters .-

		Window <- print("\n\n*** Test6: Functions as parameters\n")
		
		-- Functions - either constant functions or function variables - can be passed as parameters to other functions.		
		
		-- We define an array type with member functions as follows
		type NumberList is int[] with
		
			-- a function to put numbers in the list - we just put the index as the content here
			function Populate is 
				for each [i] in this => this[i] = i
			end
		
			-- A function to apply a function to all elements - we also give it an extra parameter 'a'
(11)		function Apply(function f(int) out (int), float a) is
				for each N in this => N = a*f(N)
			end		
			
			-- A function to print the elements of the array
			function print(cell W) is 
				W <- print("\nThe number list:\n")
				for each N in this => W <- print(" [N]")
			end 
			
		end
			
		-- We define a variable of the type with size 20
		NumberList[20] MyList
		
		-- We set the elements of the list
		MyList.Populate()
		
		-- We define a simple function
		function Square(int x) => x^2
			
		-- And call apply 
		MyList.Apply( Square, 10.0 )
		
		-- And print the result
		MyList.print(Window)
		
	end
end