+
1
|
skin
|
login
|
edit
workshop
::
helloworld
user:anonymous
{LIFTED https://www.sospc2424.ch/site/images/web4glossary/image_principale/Registre_Intel-Itanium-Processor-9500_11-554a77e149d17.jpg {def TXT [NOT,AND,OR]{br}The Maxwell's equations of Computers}} {pre {@ style="word-wrap: break-word; white-space:pre-wrap;"} '{{lambda {g n} {g g n}} {lambda {g l} {{{{lambda {n} {n {lambda {x} {lambda {z} {z {lambda {x y} y}}}} {lambda {z} {z {lambda {x y} x}}}}} l} {{lambda {x y z} {z x y}} {lambda {g l}} {lambda {g l} | {g g {{lambda {z} {z {lambda {x y} y}}} l}}}}} g l}} {{lambda {x y z} {z x y}} apple {{lambda {x y z} {z x y}} banana {{lambda {x y z} {z x y}} lemon {{lambda {x y z} {z x y}} grapes {{lambda {x y z} {z x y}} orange {lambda {f x} x}}}}}}} -> | | | | | } {center [word|abstraction|application]{br}The Maxwell's Equations of Computability} {hr} ;; {audio {@ controls style="width:100%; height:20px;"} {source {@ src="http://marty.alain.free.fr/musique/JohnSurmanTaleOfTheAncient.mp3"}}} {div {@ style="font-size:6.0em; text-align:center; color:#f00; "} Hello World} _p The '{lambda way} project is a workshop built on a wiki, '{lambda tank}, coming with a true functional programming language, '{lambda talk}. In this document we introduce '{lambda talk} as a dialect of the {b λ-calculus} implemented as a {b Turing machine}. _p Imagine a {i machine} understanding this question {pre {i replace}: "x" and "y" {i in}: "« x brave new y! »" {i by}: "Hello" and "World" } _p and answering {pre « Hello brave new World! » } _p '{lambda talk} is such a {i replacement machine} where the question is formulated using a slightly different syntax, {b '{{lambda {args} body} values}} {pre '{{lambda {x y} « x brave new y! »} Hello World} -> {{lambda {x y} « x brave new y! »} Hello World} } _p Oh no LISP is back, with its {i Lot of Insane Stupid Parentheses}! _h4 foundations _p In fact this {i strange} syntax is freely inspired by the {b λ-calculus} created in the 1930s by {b Alonzo Church} and uses the {b LISP} S-expressions introduced by {b John McCarthy} in the 1950s. _p At the foundation level a valid '{lambda talk} expression is made of a reduced set of nested terms, [{b words, applications, abstractions}] and is evaluated to {b words}. More precisely: {pre {quote expression := | word := [^\s{}]* | application := {expression1 expression2} -> words | abstraction := {lambda {words} expression} -> word }} _ul a {b word} is a group of any characters except spaces and curly braces '{}, and stands for itself, ie is its own value, _ul an {b application} determines if {b expression1} is bound to a {b function} then evaluates {b expression2} into {b words2}, applies that function to words2 and returns other {b words}, _ul an {b abstraction} is evaluated into a {b word} bound to an anonymous function selecting words (arguments) in expression (body) to be replaced by some future given words (values). _p {b Abstractions} are special forms caught and evaluated to words before {b applications} which are the remaining nested forms evaluated to words from inside out, from the leaves to the root. The list of arguments in functions determines their local working environment, there is no access to any external variables, no free variables, {b no closure}. Functions accept {b partial calls}, memorizing the given values and returning new functions waiting for the rest. Functions are {b pure} and input/output free of any side effects. Finally the evaluation stops when the string code is reduced to words without any curly braces. _p Now we can understand that this expression {pre '{{lambda {x y} « x brave new y! »} Hello World} } _p is an {b IIFE}, (Immediately Invoked Function Expression), {b '{{lambda {args} body} values}}, where the abstraction {b '{lambda {x y} « x brave new y »}} is first replaced by a word referencing a function, say {b _LAMB_xxx}, reducing the expression to {b '{_LAMB_xxx Hello World}}, which is an application replacing {b x} by {b Hello} and {b y} by {b World} and returning {b « Hello brave new World! »}. _p For convenience we add a second special form {b '{def word expression}}, first evaluating {b expression} into words then binding {b word} to these words. This special form allows to give {b name} to constant expressions and anonymous functions. This names are added to a single {b dictionary} initially empty. {pre '{def HI Hello World} -> {def HI Hello World} HI, I say '{HI} -> HI, I say {HI} } _p Note that writing {b HI} just displays {b HI} and we must {i bracket} the name between curly braces to get its value. Let's remember that in a standard spreadsheet writing {b PI} displays {b PI} and we must write this function call {b =PI()} to display the value, {b {PI}}. _p Thanks to names the previous {b IIFE} can be split into a definition followed by one or more calls {pre '{def FOO {lambda {x y} « x brave new y! »}} -> {def FOO {lambda {x y} « x brave new y! »}} '{FOO Hello World} -> {FOO Hello World} '{FOO ♥ ♣} -> {FOO ♥ ♣} } _p OK, we can play with words. And what about data structures? _h4 pairs _p Lambdas can be nested and partially called, allowing convoluted expressions {pre '{{lambda {z} {z {lambda {x y} x}}} {{lambda {x y z} {z x y}} Hello World}} -> {{lambda {z} {z {lambda {x y} x}}} {{lambda {x y z} {z x y}} Hello World}} '{{lambda {z} {z {lambda {x y} y}}} {{lambda {x y z} {z x y}} Hello World}} -> {{lambda {z} {z {lambda {x y} y}}} {{lambda {x y z} {z x y}} Hello World}} } _p These expressions return respectively the first and the second of a couple of words, {b Hello World}. {center {note_start TRACE Fine, but how does it work?}} {note_end {@ id="TRACE"} {blockquote _p 1) Lambdas are first evaluated, each returning a name bound to an {i anonymous} function added to the dictionary {pre 1: '{{lambda {z} {z {lambda {x y} x}}} {{lambda {x y z} {z x y}} Hello World}} 2: '{{lambda {z} {z _LAMB_1}} {{lambda {x y z} {z x y}} Hello World}} 3: '{{lambda {z} {z _LAMB_1}} {_LAMB_2 Hello World}} 4: '{_LAMB_3 {_LAMB_2 Hello World}} } _p 2) Then simple forms are evaluated from inside out {pre 1: '{_LAMB_3 {_LAMB_2 Hello World}} // _LAMB_2 is '{lambda {x y z} {z x y}} 2: '{_LAMB_3 {{lambda {x y z} {z x y}} Hello World}} // see below } _p {b '{{lambda {x y z} {z x y}} Hello World}} is a partial application replacing {b x} and {b y} by {b Hello} and {b World}, creating a new lambda, {b '{lambda {z} {z Hello World}}}, waiting for the third missing value and returning a reference, ie {b _LAMB_4}. {pre 3: '{_LAMB_3 _LAMB_4} // _LAMB_3 is '{lambda {z} {z _LAMB_1}} '{{lambda {z} {z _LAMB_1}} _LAMB_4} // _LAMB_4 replaces z in '{z _LAMB_1} 4: '{_LAMB_4 _LAMB_1} // _LAMB_4 is '{lambda {z} {z Hello World}} '{{lambda {z} {z Hello World}} _LAMB_1} // _LAMB_1 replaces z in '{z Hello World} 5: '{_LAMB_1 Hello World} // _LAMB_1 is '{lambda {x y} x} '{{lambda {x y} x} Hello World} // Hello replaces x in x 6: Hello // STOP } }} _p The second special form {b '{def name expression}} helps to split the two previous composed lambdas returning {b Hello} and {b World} from {b Hello World} into definitions and calls {pre '{def PAIR {lambda {x y z} {z x y}}} -> {def PAIR {lambda {x y z} {z x y}}} '{def LEFT {lambda {z} {z {lambda {x y} x}}}} -> {def LEFT {lambda {z} {z {lambda {x y} x}}}} '{def RIGHT {lambda {z} {z {lambda {x y} y}}}} -> {def RIGHT {lambda {z} {z {lambda {x y} y}}}} '{LEFT {PAIR Hello World}} -> {LEFT {PAIR Hello World}} '{RIGHT {PAIR Hello World}} -> {RIGHT {PAIR Hello World}} } _p Giving a name to {b '{PAIR Hello World}} {pre '{def BAR {PAIR Hello World}} -> {def BAR {PAIR Hello World}} '{LEFT {BAR}} -> {LEFT {BAR}} '{RIGHT {BAR}} -> {RIGHT {BAR}} } _p enlights a binary data structure whose elements are accessed using {b LEFT} and {b RIGHT} and leads to imagine more complex data structures, beginning with {b lists} {pre '{def FRUITS {PAIR apple {PAIR banana {PAIR lemon {PAIR grapes {PAIR orange NIL}}}}}} // more on NIL later -> {def FRUITS {PAIR apple {PAIR banana {PAIR lemon {PAIR grapes {PAIR orange NIL}}}}}} } _p The elements of {b FRUITS} can be accessed using {b LEFT} and {b RIGHT} {pre '{LEFT {FRUITS}} -> {LEFT {FRUITS}} '{LEFT {RIGHT {FRUITS}}} -> {LEFT {RIGHT {FRUITS}}} '{LEFT {RIGHT {RIGHT {FRUITS}}}} -> {LEFT {RIGHT {RIGHT {FRUITS}}}} '{LEFT {RIGHT {RIGHT {RIGHT {FRUITS}}}}} -> {LEFT {RIGHT {RIGHT {RIGHT {FRUITS}}}}} '{LEFT {RIGHT {RIGHT {RIGHT {RIGHT {FRUITS}}}}}} -> {LEFT {RIGHT {RIGHT {RIGHT {RIGHT {FRUITS}}}}}} } _p Displaying in sequence the elements of a list can quickly become boring. We need something to automatize the process, something to create a {b loop}. _h4 recursion _p In '{lambda talk} loops are built on {b recursion}. A recursive function calls itself inside its body until some ending condition is reached. Until now the ending char {b NIL} could be any word characterizing the end of a list, for instance {b \, '(), améliepoulain, ...} Redefining {b NIL} as a lambda will allow us to define a predicate function {b NIL?} {pre '{def NIL {lambda {f x} x}} -> {def NIL {lambda {f x} x}} '{def NIL? {lambda {n} {n {lambda {x} RIGHT} LEFT}}} -> {def NIL? {lambda {n} {n {lambda {x} RIGHT} LEFT}}} } _p and build a recursive function to display the elements of a list {pre '{def LIST.DISP {lambda {list} {{{NIL? list} {PAIR {lambda {list} } {lambda {list} {LEFT list} {LIST.DISPLAY {RIGHT list}}} }} list}}} -> {def LIST.DISP {lambda {list} {{{NIL? list} {PAIR {lambda {list} } {lambda {list} {LEFT list} {LIST.DISP {RIGHT list}}}}} list}}} '{LIST.DISP {FRUITS}} -> apple banana lemon grapes orange } _p So, thanks to the data structure [{b PAIR, LEFT, RIGHT}] and a "zest" of lambdas introducing some laziness/delay in an eager/from inside out evaluation, we could define a recursive algorithm without any {b '{IF THEN ELSE}} boolean special form, not to speak of any {b Y-combinator}, (more later). We use nothing but words, applications, abstractions and, temporarily, definitions and everything remains simple, without any shade area, any {i magic}. {center {note_start REC Fine, but how does it work?}} {note_end {@ id="REC"} {blockquote _ul 1. first evaluate special forms, lambdas and defs {pre '{def LIST.DISP {lambda {list} {{{NIL? list} {PAIR {lambda {list} } {lambda {list} {LEFT list} {LIST.DISP {RIGHT list}}} }} list}}} -> '{def LIST.DISP {lambda {list} {{{NIL? list} {PAIR _LAMB_0 _LAMB_1} list}} }} -> '{def LIST.DISP _LAMB_2} -> LIST.DISP } _ul 2. then evaluate simple forms {pre '{LIST.DISP {FRUITS}} -> LIST.DISP, alias of _LAMB_2, replaces list by FRUITS in '{{{NIL? list} {PAIR _LAMB_0 _LAMB_1} list}} -> '{{{NIL? FRUITS} {PAIR _LAMB_0 _LAMB_1} FRUITS}} then evaluates this expression 1) if FRUITS is not NIL then '{NIL? FRUITS} is RIGHT -> '{{RIGHT {PAIR _LAMB_0 _LAMB_1}} FRUITS} -> '{{RIGHT _LAMB_3} FRUITS} // memorize _LAMB_0 _LAMB_1 '{RIGHT _LAMB_3} is _LAMB_1 -> '{_LAMB_1 FRUITS} _LAMB_1 replaces list by FRUITS in '{LEFT list} '{LIST.DISP {RIGHT list}} -> '{LEFT FRUITS} '{LIST.DISP {RIGHT FRUITS}} -> apple '{LIST.DISP {RIGHT FRUITS}} -> and recurse on '{RIGHT FRUITS} until FRUITS is NIL 2) if FRUITS is NIL then '{NIL? FRUITS} is LEFT -> '{{LEFT {PAIR _LAMB_0 _LAMB_1}} FRUITS} -> '{{LEFT _LAMB_3} FRUITS} // memorize _LAMB_0 _LAMB_1 '{LEFT _LAMB_3} is _LAMB_0 -> '{_LAMB_0 FRUITS} _LAMB_0 replaces list by FRUITS in empty -> empty -> stop }}} _p Until now '{lambda talk} is supposed to ignore numbers, {b 123} or {b 3.141592653589793} are nothing but words. Yet we can compute the length of {b FRUITS} provided we accept to write numbers in an old numeral unary notation, ie {b 1 = |, 2 = | |, 3 = | | |, ...} {pre '{def LIST.LENGTH {lambda {list} {{{NIL? list} {PAIR {lambda {list} } {lambda {list} | {LIST.LENGTH {RIGHT list}}} }} list}}} -> {def LIST.LENGTH {lambda {list} {{{NIL? list} {PAIR {lambda {list} } {lambda {list} | {LIST.LENGTH {RIGHT list}}} }} list}}} '{LIST.LENGTH {FRUITS}} -> | | | | | // 5 pipes } _p In some other pages of the '{lambda way} [[workshop|?view=start]], for instance [[NIL]], we go further, defining functions reversing and concatening lists. Following {b Alonzo Church} we define the set of {b Church numbers} and their associated operators, {b NEXT, PRED, ADD, SUBTRACT, MULT, DIVIDE, MOD, POWER, GCD, ...} with which we can do some arithmetic, compute factorials, fibonaccis, and more. It's theoretically of a great interest but practically useless and out of the scope of this short introduction. _p Staying at the foundation level we are going to show that the second special form, {b '{def name expression}}, can be avoided thanks to a strange operator beloved by so many doctors in computer sciences. A small {b Y-combinator} will allow to replace the recursive definition of {b LIST.LENGTH} by an {i almost recursive} one {pre '{def Y {lambda {g n} {g g n}}} -> {def Y {lambda {g n} {g g n}}} '{def ALMOST.REC {lambda {g l} {{{NIL? l} {PAIR {lambda {g l} } {lambda {g l} | {g g {RIGHT l}}} }} g l}}} -> {def ALMOST.REC {lambda {g l} {{{NIL? l} {PAIR {lambda {g l} } {lambda {g l} | {g g {RIGHT l}}} }} g l}}} '{Y ALMOST.REC {FRUITS}} -> | | | | | // 5 pipes } _p Note that the function {b ALMOST.REC} does not contain anymore a call to its name. It's now possible to forget the names of {b Y} and {b ALMOST.REC} and create an {b IIFE} {pre '{{lambda {g n} {g g n}} {lambda {g l} {{{NIL? l} {PAIR {lambda {g l} } {lambda {g l} | {g g {RIGHT l}}} }} g l}} {FRUITS}} -> | | | | | // 5 pipes } _p Finally replacing {b NIL, PAIR, RIGHT, FRUITS} by their own definitions leads to an expression in a pure {b λ-calculus} style {pre {@ style="word-wrap: break-word; white-space:pre-wrap;"} '{{lambda {g n} {g g n}} {lambda {g l} {{{{lambda {n} {n {lambda {x} {lambda {z} {z {lambda {x y} y}}}} {lambda {z} {z {lambda {x y} x}}}}} l} {{lambda {x y z} {z x y}} {lambda {g l}} {lambda {g l} | {g g {{lambda {z} {z {lambda {x y} y}}} l}}}}} g l}} {{lambda {x y z} {z x y}} apple {{lambda {x y z} {z x y}} banana {{lambda {x y z} {z x y}} lemon {{lambda {x y z} {z x y}} grapes {{lambda {x y z} {z x y}} orange {lambda {f x} x}}}}}}} -> | | | | | // 5 pipes } _p We understand now how data structures - pairs & lists - and recursive processes which are at the core of any algorithms can be built on a minimalist set of 3 rules, [{b words|application|abstraction}]. But how does it work under the hood? _h4 implementation _p It's time to understand how applications and abstractions evaluate the input string, how the heart of '{lambda talk} is implemented. _p {b 1. Applications} replace nested forms {b '{first rest}} by words through a {b window} built on a {b single} Javascript one line function, {b eval_forms()}, working on a {b single regular expression} following the syntax of a language created by {b Stephen Cole Kleene}, a student of {b Alonzo Church} {pre °° var eval_forms = function( str ) { while (str != (str = str.replace( regexp, do_apply ))) ; return str; }; var regexp = /\{([^\s{}]*)(?:[\s]*)([^{}]*)\}/g; var do_apply = function() { var f = arguments[1] || '', r = arguments[2] || ''; return (DICT.hasOwnProperty(f))? DICT[f].apply(null, [r]) : '('+f+' '+r+')'; }; °°} _p This is what {b Ward Cunningham}, the creator of the first wiki in 1995, wrote about this implementation: {i « I was surprised that the technique worked so well in so many cases. I knew that regex are highly optimized and the cpus themselves optimize sequential access to memory which the regex must have at its core. [..] Yes, this has at its heart the repeated application of a text transformation. The fact that it is repeated application of the same transformation makes it exceptional. [..] Repeated application of Regular Expressions can perform Turing Complete computations. This works because the needed "state" is in the partially evaluated text itself. »} All is said! _p {b 2. Abstractions} use arguments as regular expressions to successively replace their occurences found in the function's body by the given words. This makes partial application native and makes closure (rather) useless. It's straightforward: {pre 0: '{{lambda {x y} « x brave new y! »} Hello World} Hello replaces the argument x in the the body, a new lambda is created waiting for the second value 1: '{{lambda {y} « Hello brave new y! »} World} World replaces the argument y in the the body, there is no more argument to be replaced, so exit 2: « Hello brave new World! » } _p Note that arguments are used as regular expressions and must be carefully chosen to avoid unwanted results, as in the following expression {pre '{{lambda {a b} « a brave new b! »} Hello World} -> {{lambda {a b} « a brave new b! »} Hello World} // ooops! } _p To be safe you should {b tag} arguments names, ie. "{b :a}" or better "{b :a:}", to avoid {b brave} to be replaced first by {b brHellove} then by {b WorldrHellove}. Generally some common sense is enough and, at least, local variables are pretty well enlighted. {pre '{{lambda {:a :b} « :a brave new :b! »} Hello World} -> {{lambda {:a :b} « :a brave new :b! »} Hello World} } _p Note also that applications work on the whole long code string - minus abstracted parts - and abstractions work on outed short strings. It's something like a tree of strings progressively transformed into words. This implementation remembers the process translated in the 1930s by an other student of {b Alonzo Church}, {b Alan Turing}, into the shape of an hypothetic machine made of a window and an infinite stripe. {img {@ src="data/brussels/turing_machine.png" width="100%"}} _p To conclude, the {b infrastructure} of '{lambda talk} can be seen as a dialect of the {b λ-calculus} implemented as a {b Turing machine}. It's still theoretically of a great interest but practically useless! _h4 conclusion _p This small infrastructure constitutes a solid and coherent foundation on which can be raised many {b superstructures} making '{lambda talk} a true programmable programming language! _ul Working in a small wiki, '{lambda tank}, a thin overlay - {b 100kb} - built upon any modern web browser and easy to install on any web provider running PHP, '{lambda talk} takes benefit of powerfull capabilities coming for free, HTML/CSS, SVG, DOM, MATHS, HTTPs, ..., and Javascript. _ul The set of special forms is extended, [{b lambda, def, if, quote, let, macro, script, style, require}]. _ul The {b dictionary} is populated with several built-in primitives: [ {code debug, browser_cache, lib, eval, apply, <, >, <=, >=, =, not, or, and, +, -, *, /, %, abs, acos, asin, atan, ceil, cos, exp, floor, pow, log, random, round, sin, sqrt, tan, min, max, PI, E, date, serie, map, reduce, equal?, empty?, chars, charAt, substring, length, first, rest, last, nth, replace, cons, cons?, car, cdr, cons.disp, list.new, list, list.disp, list.null?, list.length, list.reverse, list.first, list.butfirst, list.last, list.butlast, array.new, array, array.disp, array.array?, array.null?, array.length, array.in?, array.get, array.item, array.first, array.last, array.rest, array.slice, array.concat, array.set!, array.addlast!, array.push!, array.sublast!, array.pop!, array.addfirst!, array.unshift!, array.subfirst!, array.shift!, array.reverse!, array.sort!, @, div, span, a, ul, ol, li, dl, dt, dd, table, tr, td, h1, h2, h3, h4, h5, h6, p, b, i, u, center, hr, blockquote, sup, sub, del, code, img, pre, textarea, canvas, audio, video, source, select, option, object, svg, line, rect, circle, ellipse, polygon, polyline, path, text, g, mpath, use, textPath, pattern, image, clipPath, defs, animate, set, animateMotion, animateTransform, title, desc, br, input, iframe, mailto, back, hide, long_mult, turtle, drag, note, note_start, note_end, show, lightbox, minibox, editable, forum} ]. _ul And an extendable set of {b libraries}, directly written in the wiki and containing {b scripts}, {b styles} and {b user defined functions} is stored in wiki pages and can be called in any other wiki page, extending '{lambda talk} on demand. _p We will finish this presentation with two examples. _p The {b '{if bool then one else two}} special form helps to write readable and effective recursive functions. Using the built-in primitive {b turtle}, we can define a Lindenmeier {b L-system} structure and call it in a SVG container {pre '{def tree {lambda {:e :s :k :a :b} {if {< :s :e} then T-30 M:s T120 M:s T120 M:s T150 else M:s T:a {tree :e {* :k :s} :k :a :b} T-{+ :a :b} {tree :e {* :k :s} :k :a :b} T:b M-:s }}} -> tree '{svg {@ width="540px" height="540px"} {polyline {@ points="{turtle 400 590 180 {tree 5 200 {/ 2 3} 40 4}}" fill="transparent" stroke="red" stroke-width="1"}} ... } } {LIFTED data/brussels/turtle_20161105.jpg" {def TURTLE A Lindenmeier tree}} _p And using a Javascript code found on the web, written by a smart coder, {b Jonas Raoni Soares Silva}, and stored in a wiki page, the {b [[lib_BN]]} library, we can compute big numbers {pre {@ style="word-wrap: break-word; white-space:pre-wrap;"} '{def bigfact {lambda {:n} {if {< :n 2} then {BN.new 1} else {BN.* :n {bigfact {- :n 1}}}}}} '{bigfact 500} -> 1220136825991110068701238785423046926253574342803192842192413588385845373153881997605496447502203281863013616477148203584163378722078177200480785205159329285477907571939330603772960859086270429174547882424912726344305670173270769461062802310452644218878789465754777149863494367781037644274033827365397471386477878495438489595537537990423241061271326984327745715546309977202781014561081188373709531016356324432987029563896628911658974769572087926928871281780070265174507768410719624390394322536422605234945850129918571501248706961568141625359056693423813008856249246891564126775654481886506593847951775360894005745238940335798476363944905313062323749066445048824665075946735862074637925184200459369692981022263971952597190945217823331756934581508552332820762820023402626907898342451712006207714640979456116127629145951237229913340169552363850942885592018727433795173014586357570828355780158735432768888680120399882384702151467605445407663535984174430480128938313896881639487469658817504506926365338175055478128640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 } _p Other examples can be seen in the [['{lambda way}'s workshop|?view=start]]. Welcome! _p {i Alain Marty 2018/04/23, with my grateful thanks to} {center {img {@ src="data/fathers/Alonzo_Church.jpg" height ="{hh}" title="Alonzo Church"}} {img {@ src="data/fathers/Alan_Turing2.jpg" height ="{hh}" title="Alan Turing"}} {img {@ src="data/fathers/Stephen_Cole_Kleene.jpg" height ="{hh}" title="Stephen Cole Kleene"}} {img {@ src="data/fathers/John-McCarthy.jpg" height ="{hh}" title="John McCarthy"}} {img {@ src="data/fathers/Ward_Cunningham.jpg" height ="{hh}" title="Ward Cunningham"}} } {{hide} {def hh 110} {def LIFTED {lambda {:url :txt} {div {@ class="box lifted-corners"} {img {@ src=":url" width="100%"}} {:txt} }}} } ;; end hide {style @import url(https://fonts.googleapis.com/css?family=Quicksand); #page_view { font-family: Quicksand;} b, pre { font:bold 1.0em courier new; color:#400; } .box { position:relative; top:0; left:0; padding: 10px; border: solid 1px #555; background-color: #eed; width: 580px; margin:20px auto; border-radius:5px; text-align:center; } .lifted-corners { box-shadow: 0px 10px 8px rgba(0,0,0,0.8); } .lifted-corners:after { content: ""; position: absolute; width: 100%; height: 20px; bottom: -20px; left: 0px; background-image: -moz-radial-gradient( center bottom, ellipse farthest-side, rgb(250, 250, 250) 80%, rgba(250, 250, 250, 0) 100%); background-image: -webkit-radial-gradient( center bottom, ellipse farthest-side, rgb(250, 250, 250) 80%, rgba(250, 250, 250, 0) 100%); background-image: -o-radial-gradient( center bottom, ellipse farthest-side, rgb(250, 250, 250) 80%, rgba(250, 250, 250, 0) 100%); } }