Il linguaggio di programmazione Falcon, abbreviabile come Falcon P.L., Falconpl o Falcon, è un linguaggio di scripting multi paradigma open source, sviluppato da Giancarlo Niccolai, inizialmente nell'ambito della propria attività lavorativa, e pubblicato successivamente come progetto Open Source.

Storia Le origini risalgono a un progetto denominato HASTE, avviato nel 2002 (Haste Advanced Simple Text Evaluator). Si trattava di un motore di scripting pensato principalmente per l'inclusione in applicazioni dinamiche e che mirava a realizzare una macchina virtuale veloce e leggera, con capacità di generare un alto throughput di dati a basse latenze in ambienti fortemente multithreading, per la configurazione dinamica di applicazioni in ambito finanziario. Successivi adattamenti e implementazioni, basate principalmente sull'esperienza e sulle necessità operative, e sui suggerimenti dei primi utenti hanno dato origine al Falcon attuale. La prima versione distribuita attraverso pacchetti ufficiali su distribuzioni di software Open Source è stata la 0.8.8, nel maggio del 2008.

Filosofia Falcon fonde diversi stili di programmazione (detti paradigmi) in un unico ambiente.

Da un punto di vista implementativo Falcon si propone come linguaggio stand-alone ma con una forte predisposizione verso la cooperazione con applicativi preesistenti, meglio ancora in ambito multi-threading.

Hello World Il seguente esempio propone il classico "Hello, World!" utilizzando l'operatore di stampa veloce ">", o, in forma più classica, attraverso la funzione printl:

> "Hello World!" printl("Hello World again!")

Falcon supporta tutti caratteri Unicode negli script sorgenti:

// International class; name and street class 国際(なまえ, Straße) // set class name and street address नाम = なまえ شَارِع = Straße // Say who am I! function 言え!() >@"I am $(self.नाम) from ",self.شَارِع end end // all the people of the world! 民族 = [ 国際( "高田 Friederich", "台湾" ), 国際( "Smith Σωκράτης", "Cantù" ), 国際( "Stanisław Lec", "południow" ) ]

for garçon in 民族: garçon.言え!()

Tipi di dati Nil - Il classico "non valore". Integer - intero a 64 bit. Numeric - Numero in virgola mobile a 64 bit compatibile con le specifiche IEEE. Range - una tripletta costituita da un limite inferiore, uno superiore e un passo. MemBuf - Buffer di memoria dove ogni elemento è un intero senza segno costituito da 1, 2, 3 o 4 bytes. Function - Le classiche funzioni, ovvero porzioni di codice richiamabili in altre parti del programma. String - Sequenza mutabile di caratteri UNICODE. Array - Sequenza mutabile di istanze di varia natura. Dictionary - Sequenze mutabili costituite da coppie chiave-valore. Oggetti - Istanze derivate da classi o oggetti privi di classe di derivazione. Class - Classi che possono generare istanze (oggetti). Method - Sono le funzioni da applicare su una qualsiasi istanza.

Paradigmi supportati Falcon supporta al momento 6 paradigmi di programmazione: procedurale funzionale orientato agli oggetti (OOP) OOP basata sui prototipi orientata ai messaggi programmazione tabellare

Procedurale La programmazione procedurale si basa sulle classiche dichiarazioni e chiamate di funzioni. Tutte le funzioni supportano implicitamente un numero variabile di parametri, oltre a parametri posizionali/nominali.

Il seguente è un programma completo, interamente procedurale, che illustra alcune decisioni implementative che riguardano la struttura dei loop e il processamento imperativo delle sequenze:

function sayList(saying) for elem in saying >> elem formiddle: >> " " forlast: > "!" end end

sayList(List("Have", "a", "nice", "day") )

Funzionale Il motore di valutazione chiamato Sigma-riduttore interno permette di scrivere programmi in puro stile funzionale, non diversamente da quanto possibile fare in linguaggi come il Lisp. Comunque resta possibile miscelare diversi stili di programmazione (come approcci OOP o puramente imperativi) direttamente nelle sequenze funzionali.

Le sequenze funzionali sono costituite da normali array; questo significa che possono essere create, controllate e modificate dinamicamente dal programma stesso, sia fra valutazioni distinte che durante il corso di una valutazione di Sigma-riduzione. Il seguente esempio illustra la possibilità di cambiare dall'interno della sequenza un particolare tipo di referenza a variabile, chiamato late binding.

pnext = [ printl, '"', &value, '"' ]

dolist(function(p); seq.value = p; eval(seq); end, ["Have", "a", "nice", "day"] )

È anche possibile chiamare direttamente le sequenze funzionali se il loro primo membro è esso stesso un valore chiamabile, come nel seguente esempio:

[printl "Prompt> "]( "Real data to print" )

Sequenze funzionali con un solo livello di profondità (come quelle viste in questi esempi) possono essere viste come chiamate pre-registrate (cached calls), e una volta assegnate a una variabile sono morfologicamente equivalenti ai simboli che identificano le funzioni stesse.

Il paradigma funzionale è completato dal marcatore di valore fuori banda (out of band item marker). Ogni valore può ricevere il marcatore oob, la cui presenza può essere verificata in un secondo momento attraverso operatori di linguaggio e/o funzioni di libreria. Si tratta di un segnale che indica una significanza speciale del valore marcato e che può alterare il comportamento delle sequenze funzionali attraverso le quali viene fatto viaggiare. Per esempio molti cicli funzionali, come floop o times, possono essere fatti ripartire o interrotti ritornando rispettivamente un valore oob 1 o 0 da una delle funzioni della sequenza. Oppure, la funzione map, che trasforma tutti i valori contenuti in una sequenza applicandovi una funzione di trasformazione, ignorerà (scartandolo) un valore nil marcato oob; in questo modo è possibile effettuare operazioni di mappatura e filtro in un singolo passaggio.

Orientato agli oggetti (OOP) Falcon adotta un modello di Programmazione orientata agli oggetti che include classi, ereditarietà, membri statici, inizializzatori di proprietà e costruttori. L'ereditarietà multipla è supportata a condizione che al massimo una delle classi nella gerarchia sia riflessiva (cioè, rifletta dati direttamente forniti dalle applicazioni o dai moduli di terze parti in modalità nativa). È supportato anche l'accesso ai membri delle classi base.

La struttura delle istanze è fissa e immutabile, ma data la natura funzionale di Falcon, che vede le funzioni come un tipo particolare di dati, è possibile impostare i membri di un'istanza come semplici dati o come funzioni (rendendoli quindi metodi) dinamicamente. Sequenze funzionali possono essere assegnate alle proprietà, nel cui casi diventano metodi funzionali per la data istanza.

Falcon supporta oggetti primitivi, ossia oggetti non dotati di classe specifica, che possono essere sia totalmente privi di classe che parzialmente derivati da una o più classi parente. Gli oggetti primitivi vengono istanziati e impostati prima che la Macchina Virtuale esegua il programma principale. L'ordine di risoluzione delle istanze è gestito dal Linker di Falcon, che assicura una corretta inizializzazione pur in caso di referenze incrociate fra oggetti primitivi nello stesso modulo.

Le classi possono essere istanziate anche attraverso sequenze funzionali: l'istantazione di una classe (la creazione di un'istanza) è morfologicamente equivalente a "chiamare" il simbolo della data classe. Quindi, valutare una sequenza funzionale che comincia con il simbolo di una classe equivale a chiederne l'istantazione.

Il modello OOP di Falcon è completato dal sovraccarico degli operatori, che permette di creare classi sulle quali gli operatori di linguaggio logici e matematici si comportano in modo specializzato.

OOP basata sui prototipi La programmazione orientata ai prototipi è simile al paradigma OOP, ma è privo del concetto di classe. Le istanze sono tutte prive di classe e la loro struttura può essere cambiata dinamicamente. I dizionari del linguaggio Falcon (tipo primitivo costituito da collezioni di coppie chiave univoca/valore) possono includere funzioni; possono quindi essere blessed, informando così il sistema che sono da trattare come istanze prive di classe. Applicando l'accessore punto è possibile accedere ai loro membri come metodi o proprietà. Nel seguente esempio un dizionario diventa oggetto:

dict = bless([ 'state' => 0, 'incme' => function(); self.state++; end ]) dict.incme() > dict.state // stampa '1'

Un meccanismo simile può essere applicato agli array tramite i binding:

array = [1,2,3] array.showMe = function() for item in self: > item end

array.showMe()

Orientata ai messaggi La programmazione orientata ai messaggi è costituita dall'invocazione di uno o più gestori alla trasmissione di un messaggio. Il contenuto del messaggio è arbitrario, e può essere costituito da qualsiasi tipo di dato, incluse classi (dalle quali creare istanze al volo), sequenze funzionali o tabelle. I ricevitori possono competere per ricevere il messaggio escludendo gli altri gestori, o possono partecipare nella costruzione di una risposta comune in passaggi ordinati. I messaggi possono richiedere una gestione immediata all'atto della trasmissione, oppure essere lasciati nell'ambiente per essere ricevuti e gestiti da sottoscrittori tardivi (così dette "asserzioni").

La programmazione orientata ai messaggi si interfaccia direttamente con la Macchina Virtuale, con la quale moduli nativi e applicazioni parente possono interagire. Per esempio un'applicazione multithreaded può lanciare nella macchina virtuale messaggi che giungono da thread differenti per una gestione serializzata a livello di script, e una susseguente ri-trasmissione asincrona dei messaggi elaborati direttamente dall'interno degli script.

Programmazione tabellare La programmazione tabellare è una speciale estensione e generalizzazione dell'OOP, dove una classe è rappresentata da una tabella, le cui colonne sono proprietà e ogni riga è un'istanza. Oltre a mantenere tutte le istanze ordinatamente assieme, e permettere a ogni istanza di lavorare con quelle adiacenti accedendo alla tabella madre, modifiche sulla struttura della tabella sono immediatamente riflesse su tutte le istanze.

Le tabelle offrono un supporto diretto alla selezione di un comportamento o un insieme di comportamenti da una serie finita di scelte possibili, fornendo la base per sviluppare direttamente nel linguaggio motori decisionali a logica discreta o fuzzy. Ogni riga, fisicamente costituita da un dato di tipo Array, può contenere sia dati e logiche specifiche della tabella che dati o logiche private dell'istanza (grazie ai Binding), quindi un'entità selezionata da una logica di estrazione globale può fornire comportamenti altamente specializzati.

Altre caratteristiche Oltre a fornire diversi stili di programmazione Falcon presenta caratteristiche trasversali a diversi modelli; di seguito sono indicate le più rilevanti.

Descrizioni di liste Tipi di base e classi che espongono un'interfaccia di sequenza verso il motore interno offrono un metodo "comp", che permette di sintetizzare insiemi a partire da una definizione matematica.

Il metodo "comp" richiede l'indicazione obbligatoria di un parametro "origine", che può essere un'altra sequenza, un valore di tipo "range" o una funzione generatrice che torna un valore per volta, e un marcatore speciale per dichiarare la fine della sequenza.

Una funzione (o in generale un'entità chiamabile) può essere fornito come parametro opzionale e agisce sia come filtro che come modificatore.

Sequenze associative (per esempio dizionari) sono supportati.

Il seguente è un semplice esempio che usa un range per creare un array contenente i numeri pari tra 2 e 10, attraverso comprensione di lista:

even_array = [].comp( [2:11:2] )

Per generare una lista di numeri interi casuali fra 1 e 9, di lunghezza anch'essa casuale: