| zone |
An object that has methods for representing a region of the world.
Coordinates used for this are latitude and longitude (latitude has first ordinality) and are expressed in degrees.
The region is not required to be continuous.
Inherits from shape .
Additional members of zone
| own |
Returns the default locale set on the computer, or nil if there is none.
|
Additional methods of zone
| time |
timezoneString timezoneStrings |
Returns a zone representing the region of the timezone named in
the parameters, each of which should be in Olson timezone format.
var z = zone.time["America/Los_Angeles" "America/New_York"] ; returns a zone containing both the PST and EST time zones.
|
| language |
languageCode languageCodes |
Returns a zone representing the region where the languages given are official languages.
The languages should be in ISO 639-2 format.
Throws an exception if a language doesn't exist.
var z = zone.language["eng" "fre"] ; the zones where english and french are official languages
|
| language |
timeObject languageCode languageCodes |
Just like the above except it takes into account the time given.
var z = zone.language[time[-500-01-01] "eng"] ; the regions where english was spoken at 500 BCE
|
| country |
countryCode countryCodes |
Returns a zone representing the region where the countries given are (at the time the function is called).
The countries should be in ISO 3166 format
(all representations are accepted and case doesn't matter).
Throws an exception if a country doesn't exist.
var z = zone.country["arm" "cn"] ; a zone containing the regions of China and Armenia
|
| country |
timeObject countryCode countryCodes |
Just like the above, except it takes into account the time given.
var z = zone.country[time[-500-01-01] "arm" "cn"] ; a zone containing the regions of China and Armenia
|
Additional members of zone! objects
| timezones |
A list of tz database timezones intersecting the zone (in Olson timezone format).
|
Additional methods of zone! objects
| languages |
timeObject |
A list of langauges spoken within a zone in order of popularity
(how many people speak it) during a given time.
If the time is omitted, the current time (when the method is called) is assumed.
|
| countries |
timeObject |
A list of countries within a zone during a given time.
|
| collations |
timeObject |
A list of collations within a zone during a given time.
|
|
An object that has methods for representing and operating on date and time.
The internal representation is intended to be continuous and unambiguous
(without oddities introduced by time zones, daylight savings time, or leap-seconds).
The unambiguous internal representation can, of course, be converted to any time format.
Pseudo-members:
| after: statements |
A macro that returns a time object representing the current time directly after the statements have run.
Time capturing is still allowed on machines that don't have the actual date or time availble to it,
as long as the times are only used to create durations.
In Lima, there is no "now".
Because statements can be reordered to optimize performance, there would be no
way to guarantee that a time is captured both after its preceding statements
and before its following statements without destroying lima's ability to reorder
statements optimally.
So instead, Lima allows you to choose to either bind a time before some given statements
or before them, but not both.
If you need both, you need to create two separate time objects.
; the following to sets of statements can be run completely in parallel
; which means that ta1 may be the exact same time as tb1
; and also means that tb2 might even be earlier than ta2 (if 'B.txt' takes less time to download and output)
var ta1 = time.before:
var ta2 = time.after:
var x = downloadFile['A.txt']
outputFile['some/path/A.txt', x]
var tb1 = time.before:
var tb2 = time.after:
var y = downloadFile['B.txt']
outputFile['some/path/b.txt', y]
; the following statement guarantees that the time is as close the console output as possible
var t = time.before:
wout['The time is: 't]
; the following statement throws an exception because t isn't declared until after the wout statement
var t = time.after:
wout['The time was: 't]
|
| before: statements |
A macro that returns a time object representing the current time directly before the statements have run.
|
| resolution |
A duration object representing the time between "ticks"
(between value changes of the clock - ie values of now and ).
|
| accuracy |
A probability map describing how accurate the clock is likely to be at the moment this member is accessed.
The object maps the likely difference, in seconds, to its probability (value from 0 to 1).
; if there's at least a 90% likelyhood the absolute deviation is less than half a second
if time.accuracy[[-.5<k.5]] < .9:
resyncClock[] ; made up function that might resync the clock from the internet
|
| tai | Same thing as time.ts['tai'] . |
| utc | Same thing as time.ts['utc'] . |
| unix | Same thing as time.ts['unix'] . |
| tdb | Same thing as time.ts['tdb'] . |
Methods:
|
|
Returns a type that enforces variables to hold time objects.
time! t = time[2012/04/04 3:00']
|
| ts |
timeStandardCodename |
Returns an object with methods for handling dates for a particular time standard.
A list of time-standards is below this section.
When used with this method, the time standard names are case insensitive.
|
|
|
The same bracket operator time.ts['tai'] has.
|
| p |
pattern |
Macro that creates a time-pattern that can be used to both parse and format time objects.
Keywords and strings are concatenated to describe the pattern.
The $ operator can be used to indicate that more than one pattern can be used.
Parts that are surrounded by parentheses are optional (the whole thing in parens should match, or none of it).
Expressions with parens are nestable.
time.p[M ' - ' d] ; parses something like "April - 2"
time.p[h ':' min (':' s)] ; can parse both "8:30" and "8:45:20"
time.p[h (':') min ((':') s)] ; can also parse "830", "8:4520", "845:30", and "84520"
time.p[M '-'$' - ' d] ; can parse both "April-2" and "April - 2"
time.p[h$hp ':' min] ; can parse both "8:30" and "08:30"
To use the value of an expression inside time.p ,
prefix the expression with the @ sign.
Time patterns can be used with this method as well as stringable values.
var x = ":"
var timeOfDay = time.p[h @x min @cat["|"x"|"] s]
time.p[y'.'m'.'d' ' @timeOfDay] ; matches '2000.04.03 3:05|:|00'
Any keyword can also be explitly set with the equals sign.
If such an assignment is done, that keyword is ignored when parsing
time['20:00' time.p[M='April' h min] ]
The available keywords and their meanings depend on the particular time-standard being used.
|
| addSimilar |
originalStandard newStandard convertTo convertFrom |
Adds a time standard named newStandard based on originalStandard .
The new time-standard and the result of a timeObject s' ts
method for the new standard will have all the same members and methods as the originalStandard .
|
| add |
name staticMembers standardObject |
Adds a time standard from scratch, named name .
staticMembers should be an object containing the members you'll get when you access
time.ts[name] .
standardObject should be a function that takes a time! object and
returns the object you'll get when you access timeObject.ts[name] .
|
Time objects
Time objects represent a non-ambiguous date and time.
Time objects do not carry information about any particular location or zone,
they represent the absolute time.
A time-object can be converted to any representation, even ambiguous representations, for any zone or area.
time objects have the following methods:
| format |
timePattern timeZone translator |
The same format method timeObject.ts['tai'] has.
|
| ts |
timeStandardCodename |
Returns an object with methods for creating modified time objects from the current one related to the given time-standard.
|
time objects have the following members:
| tai | Same thing as timeObject.ts['tai'] . |
| utc | Same thing as timeObject.ts['utc'] . |
| st | Same thing as timeObject.ts['st'] . |
| unix | Same thing as timeObject.ts['unix'] . |
| tdb | Same thing as timeObject.ts['tdb'] . |
time objects have the following binary operators:
| a - b |
Difference of times and subtractions of durations.
time[2012/04/05 20:00] - time[2012/04/05 17:00] ; returns an object equal to dur[3:00]
time[2012/04/05 20:00] - dur[3:00] ; returns an object equal to time[2012/04/05 17:00]
|
| a < b |
Time less than.
time[2012/04/05] < time[2012/04/01] ; returns false
|
| a <= b |
Time less than or equal to.
|
| a > b |
Time greater than.
time[2012/04/05] > time[2012/04/01] ; returns true
|
| a >= b |
Time greater than or equal to.
|
Time standards
Time standards in Lima include both a proper time-standard (a definition of a second under various circumstances)
and a calendar standard.
A particular time standard will have different but similar methods for parsing dates
Standards should usually have a default pattern so the pattern need not be more fully specified,
but some standards have the ability to further define a particular date pattern.
| tai |
International Atomic Time (TAI) is a coordinate time standard.
Members available to time.ts['tai']
| years |
A set of dates each representing the beggining of a year in TAI in ascending order.
wout[time.tai.years[0]] ; prints "0001-01-01 00:00:00"
df time.tai.years[[time[1999] < v < time[2020]]] y:
wout["It was a dark and storymy night, in the year "y.tai.y"...."@]
|
| months |
A set of dates each representing the beggining of a month in TAI in ascending order.
|
| weeks |
A set of dates each representing the beggining of a week in TAI in ascending order.
Days of the week start on monday.
wout["Lets say all the days of the week. Together now:"@]
var someWeek = time.tai.weeks[0]
df (1..7).map[someWeek+ v*dur[/1]] v
wout[v.tai.format[time.p["DW"]]@]
|
| days |
A set of dates each representing the beggining of a day in TAI in ascending order.
|
| hours |
A set of dates each representing the beggining of an hour in TAI in ascending order.
|
| minutes |
A set of dates each representing the beggining of a minute in TAI in ascending order.
|
| seconds |
A set of dates each representing the beggining of a second in TAI in ascending order.
|
Note: In all the above members (years, months, weeks, days, hours, minutes, and seconds),
key 0 maps to the first of that time-unit starting in year 1 CE, and negative keys map to earlier time-units.
Methods and operators available to time.ts['tai']
|
dateString timePattern translator |
Returns a time object of the dateString as parsed with timePattern.
If the string can't be parsed, an exception is thrown.
If no pattern is passed in, the default pattern is
time.p[(y('.'m$mp('.'d$dp))) (h$hp':'min(':'sr)) (' 'era)] .
translator (optional) is a function that takes in one of the english date-words and returns a word from another language.
time.ts['TAI']["2012/06/04 15:00"] ; June 4th, 2012 at 3:00pm TAI
time.ts['TAI']["20120604" time.p[ympdp]] ; June 4th, 2012 (at 0:00, ie 12:00am) TAI
var translator = fn x:
if x.lower=='april': ret 'shigatsu'
else ret x
time.ts['TAI']["20120403" time.p[ympdp] translator] ; shigatsu 4th, 2012 (at 0:00, ie 12:00am) TAI
Will throw a parse exception if the string can't be parsed properly.
Will throw a contradictory-date exception if the string is parsed properly,
but certain redundant information is inconsistent.
; the following throws an exception, since April 3rd 2012 was a Tuesday
time.ts['TAI']["Saturday, April 3, 2012" time.p[DW', 'M' 'd', 'y]]
timePattern should be a value returned from time.p .
|
|
dateText |
Like the bracket operator above, but instead of being passed a string, is instead passed literal text.
Always uses the default for timePattern
from the bracket operator with two parameters and doesn't use a translator.
time.ts['TAI'][2012.06.04 15:00] ; June 4th, 2012 at 3:00pm TAI
|
Time pattern keywords available to time.ts['tai']
The following keywords can be used in the pattern.
Keywords are either all uppercase or all lowercase.
Uppercase keywords always describe a part of time as a word (e.g. 'April' or 'Saturday').
| era |
The era (either 'CE' or 'BCE').
|
| cent |
The century (eg. time['1994'].cent would return 20) |
| y |
The absolute value of the year (for use with era eg. "300 BCE").
|
| yp |
Shortened year or "year, pivoted" (eg. "96" instead of "1996").
Must be accompanied by the py keyword.
|
| py |
Pivot year - two digit years of this number or lower are interpreted as in the current century.
Two-digit years above this number are interpreted as in the previous century.
; usually, this should be explicitly set rather than matched in a formatted string
time['04/03/50' time.p[py=50 mp'/'dp'/'yp]] ; same as time[2050.4.3]
time['04/03/51' time.p[py=50 mp'/'dp'/'yp]] ; same as time[1951.4.3]
|
| dy |
The numeric day of the year (e.g. 51 for time[1986/02/20]) |
| m |
The numeric month where 1 is Januray and 12 is December. |
| mp |
The numeric month padded with 0 for months less than 10 (eg. 08 instead of 8). |
| M |
The month as a word (eg. February). |
| MS |
The month as an abbreviation (eg. Feb). |
| d |
The numeric day of the month (eg. 20 for time[1993/04/20]). |
| dp |
The numeric day of the month padded with 0 for days less than 10
(eg. 08 instead of 8). |
| dw |
The numeric day of the week where 1 is Monday and 7 is Sunday |
| DW |
The day of the week as a word (eg. 'Tuesday') |
| DWS |
The day of the week as a short word (eg. 'Tue') |
| h |
Hour in 24-hour time (eg. 15 for 3:00 pm) |
| hp |
Hour in 24-hour time padded with 0 for hours less than 10 (eg. 08 instead of 8). |
| hh |
Hour in 12-hour time (eg. 3 for 3:00 pm) |
| hhp |
Hour in 12-hour time padded with 0 for hours less than 10 (eg. 08 instead of 8). |
| MER |
Meridian indicator (eg. "am"). |
| min |
Minute. |
| s |
Integer second. |
| sr |
Real number second, includes fractional seconds in the decimal of the number
(eg. "8:01:23.54").
|
TAI members of time-objects: timeObject.ts['tai']
Have the following members:
| era | The era as a string. |
| y | The number of years. |
| m | The number of months. |
| d | The number of days. |
| h | The number of hours. |
| min | The number of minutes. |
| s | The number of seconds, possibly fractional. |
| time | Returns a time object representing the (now possibly modified) tai time. |
The members era , y , m , d , h ,
min , s can be modified, and modifying them mutates the tai object.
Also, for the numeric members (all except era ),
setting the member to a negative number will roll back that many extra time-units before the next highest time-unit,
and setting the member to a number greater than is allowed inside the next highest time-unit adds that many extra time-units.
var x = time[2008/04/03 2:30:00].tai
x.s -= 40 ; x now represents time[2008/04/03 14:29:20]
x.min += 100 ; x now represents time[2008/04/03 15:09:20]
x.y -=1 ; x now represents time[2007/04/03 15:09:20]
x.time ; returns time[2007/04/03 15:09:20]
; note that subtracting a year as a duration is different from subtracting it as a part of a TAI time
; since things like leap years need to be taken into account
var y = time[2008/04/03 3:09:20] - dur[1/] ; time[2007/04/04 9:09:20]
Has the following methods:
| format |
timePattern translator |
Returns a string representation of the time according to the stringFormat.
If no pattern is passed in, the default is time.p[y'/'m'/'d' 'h':'min':'s' 'tz] .
translator should be able to translate from the english forms of each non-numeric keyword
(era M MS DW DWS MER).
|
|
| st |
Standard Time. A locality specific time-standard.
Has all the members and time-pattern keywords that time.ts['tai'] has, as well as the following additional ones:
Members available to time.ts['st']
| jumps |
A list of time objects each representing the time after a non-linear time jump (usually daylight-savings time jumps).
The list is sorted by increasing time.
var now = time.before:
var nextTimeJump = time.st.jumps[[v>now]][0]
var timeOneSecondBeforeJump = nextTimeJump
timeOneSecondBeforeJump.st.s -= 1
|
Methods and operators available to time.ts['st']
|
dateString timePattern translator |
Exactly like the bracket operator(s) of time.ts['tai'], except supports time-zones.
If no time zone keyword is used to parse the string (tz, TZN, tzo, or tzom), UTC is assumed.
If no pattern is passed in, the default pattern is
time.p[(y('/'m$mp('/'d$dp))) (h$hp':'min(':'sr)(' 'tz))] .
time.ts['st']["2012/06/04 15:00"] ; June 4th, 2012 at 3:00pm UTC
time.ts['st']["2012/06/04 15:00 America/Los_Angeles"] ; June 4th, 2012 at 3:00pm PST
time.ts['st']["20120604" time.p[ympdp]] ; June 4th, 2012 (at 0:00, ie 12:00am) UTC
time.ts['st'][2012/06/04 15:00] ; June 4th, 2012 at 3:00pm UTC
time.ts['st'][3:00] ; 3 hours in to the common era (usually the meaning is as a duration of 3 hours)
|
Time pattern keywords available to time.ts['st']
Has all the time-pattern keywords that time.ts['tai'] has, with the following additions:
| tz |
Time zone as a local time-zone id (eg. "America/Los_Angeles"). |
| TZN |
Time zone as a name (e.g. "Pacific Standard Time"). |
| tzo |
Time zone offset - whole number of hours offset from UTC, including sign (e.g. "-3" in "April 3 2401 8:00 Z-3:30"). |
| tzop |
Time zone offset, padded. (e.g. "-03" in "April 3 2401 8:00 Z-03:30"). |
| tzom |
Time zone offset minutes - whole number absolute value of minutes offset from UTC (e.g. "30" in "April 3 2401 8:00 Z-3:30"). |
Note about time zones: time-zone abbreviations are non-standard and in some cases ambiguous, so Lima doesn't support them.
UTC members of time-objects: timeObject.ts['st']
Has all the members timeObject.ts['tai'] has with the following addition:
Has all the methods timeObject.ts['tai'] has.
Note that any translater used should also be able to translate time zone names and abbreviations.
Has the following methods:
| format |
timePattern timezone translator |
Just like the format methods for tai time-objects (timeObject.tai ),
except a timezone can be passed.
If no timeZone is passed in, the default is UTC.
|
|
| utc
ut0
ut1
ut1r
ut2
ut2r
|
Versions of Universal Time.
These have all the same methods, operators, and time-pattern keywords time.ts['tai'] has.
|
| GPS |
Global Positioning System Time
|
| TT |
Terrestrial Time
|
| TDB |
Barycentric Dynamical Time - a relativistic dynamical time.
|
| UNIX |
Unix timestamp, the number of TAI seconds since Jan 1, 1970 00:00 UTC.
Methods and operators available to time.ts['unix']
|
unixTimestamp |
Returns a time object representing the time indicated by unixTimestamp.
unixTimestamp must be a number (can be fractional).
var x = int.parse[915148801.25]
time.ts['unix'][x] ; 1999/01/01 00:00:33.25 TAI
time.ts['unix'][1095379200] ; 2004/09/17 00:00:32 TAI
|
Members available to timeObject.ts['unix'] for a given time-object
| stamp |
Returns the unix timestamp as a number.
var t = time.ts['unix'][1095379200.54] ; 2004/09/17 00:00:32.54 TAI
t.ts['unix'].stamp ; returns<PHONE_NUMBER>.54
|
| s |
The integer timestamp rounded to the nearest second (standard unix timestamp).
var t = time.ts['unix'][1095379200.54] ; 2004/09/17 00:00:32.54 TAI
t.unix.s ; returns<PHONE_NUMBER>
|
| ms |
The integer timestamp rounded to the nearest millisecond.
|
|
Note that when dealing with a date format with ambiguous dates for a particular time-standard,
the earliest possible time is assumed to be the one meant.
|
| dur |
An object that has methods for representing and operating on durations.
Uses a constant definition for date parts:
- y: year = 365.25 days (A Julian Year) or 12 months
- m: month = 30.4375 days (Based on the Julian Year)
- week = 7 days (168 hours)
- d: day = 24 hours (86,400 seconds)
- h: hour = 60 minutes
- min: minute = 60 seconds
- s: second = an SI second
Methods:
|
|
Returns a type that enforces variables to hold dur objects.
dur! t = dur[3:00]
|
|
duractionText |
Returns a dur object of the duractionText parsed as a duration.
The pattern it parses off of is
time.p[(y'/')$((y)'/'m('/'d))$('/'d) s$((h)':'min':'(s))$(h':'(min))] .
dur[15] ; 15 seconds
dur[15.45] ; 15.45 seconds
dur[150] ; 150 seconds
dur[:15:0] ; 15 minutes
dur[:15:] ; 15 minutes
dur[15:0] ; 15 hours
dur[-7:00] ; negative 7 hours
dur[0:30.5] ; 30 minutes and a half (30 minutes and 30 seconds)
dur[:30:29.56] ; 30 minutes, 29 seconds, and 560 milliseconds
dur[/5/1] ; 5 months and 1 day
dur[23/9] ; 23 years and 9 months
dur[10/] ; 10 years
dur[-23/10/1] ; negative 23 years, 10 months, and 1 day
dur[5/1] ; 5 years and 1 month
dur[2/8/23 3:00:00.05] ; 2 years, 8 months, 23 days, 3 hours, and 50 milliseconds
dur["00"] is a special case indicating an infinite amount of time.
Note that dur[00].time == time[00] .
|
|
duractionString |
Like the bracket operator above, but instead of writing literal duractionText,
a string is passed instead.
Uses the same pattern from the bracket operator with the duractionText parameter.
If the string can't be parsed, an exception is thrown.
dur["15:00"]
dur[":30:29.56"]
dur["-23/10/1"] ; same as dur[-23/10/1]
|
Duration objects
Duration objects represent a length of time.
dur objects have the following members:
| str |
The to-string member.
The pattern output is: time.p[y'/'m'/'d' 'h':'min':'sr]
|
dur objects have the following methods:
|
timePatternKeywords |
Returns an object that counts the time only in terms of the parts given in the timePatternKeywords.
timePatternKeywords are similar to the input into time.p .
The resulting object will have members (of the same name as the time pattern part)
representing the amount of time where, all added up, they equal the original duration.
The smallest time unit can have a fractional value, all others will be integers.
dur[15:00][d h s] ; returns {d=0 h=15 s=0}
dur[34 4:34:23.4][h min] ; returns {h=820 min=34.39}
var pattern = time.p[y'-'m'-'d]
dur[3/4/20][@pattern] ; you can use time.p objects like that: returns {y=3 m=4 d=20}
| y | The number of years. |
| m | The number of months. |
| w | The number of weeks. |
| d | The number of days. |
| h | The number of hours. |
| min | The number of minutes. |
| s | The number of seconds. |
|
dur objects have the following binary operators:
| a + b |
Addition of durations, or addition of a time and a duration.
time[2012.04.05 20:00] + dur[3:00] ; returns an object equal to time[2012/04/05 23:00]
dur[3:00] + dur[..24] ; returns a dur object representing 24 days and 3 hours
|
| a - b |
Subtraction of a duration.
time[2012.04.05 20:00] - dur[3:00] ; returns an object equal to time[2012/04/05 17:00]
dur[..24] - dur[3:00] ; returns a dur object representing 23 days and 21 hours
|
| a * b |
Duration multiplication. Multiplies a duration.
One of the operands must be a dur object, and the other must be a number.
3*dur[2:20] ; returns an object equal to dur[7:00]
|
| a / b |
Duration division. Divides a duration.
The first operand must be a dur object and the second must be a number.
dur[1:00]/4 ; returns an object equal to time[0:25]
|
| a < b |
Less than comparison between durations.
dur[1:00] < dur[4:00] ; returns true
|
| a <= b |
Less than or equal to comparison between durations.
|
| a > b |
Greater than comparison between durations.
dur[1:00] > dur[4:00] ; returns false
|
| a >= b |
Greater than or equal to comparison between durations.
|
time objects have the following unary operators:
| -a |
Negative. Returns the negative of the duration.
-dur[1:00] ; returns negative 1 hour
var x = dur[-2:00] ; negative 2 hours
-x ; 2 hours
|
|
| dns |
Object containing methods for dns address lookup.
Note that this ignores any system-defined names (/etc/hosts and the like), so if you want those to be included,
use system.host .
The behavior of this object can be overridden using the dnsObject attribute.
Has the following methods:
| [] |
name type class
edns=false deviceAddress=adr.own[0] |
Returns the dns answer to the query.
name is the name of the resource you want to query about (usually either a hostname or an ip address).
type is the the dns rrtype ('A', 'AAAA', 'MX', 'TXT', 'SRV', 'PTR', 'NS', 'CNAME', 'SOA', 'NAPTR', etc).
class is the numerical representation of the class of service (usually 1 for internet).
deviceAddress (optional) is the address of the device to query from.
The answer is an object that looks like:
{ header= {
qr=<query response>
opcode=___
aa=<authoratative answer>
tc=<truncation bit>
rd=<recursion desired>
ra=<recursion available>
rcode=<response code>
}
answer=___
authority=___
additional=___
}
where answer , authority , and additional are lists of resource record objects that look like this:
{ type=<dns rrtype>
ttl=___
... ; additional fields
}
The additional fields in resource record objects are the following (by type)
| type |
properties |
| SOA |
- primary
- admin
- serial
- refresh
- retry
- expiration
- minimum
|
| A and AAAA |
- address - an address (
adr! objects) for the host hostString.
|
| MX |
- priority
- exchange - the hostname of the mail exchange
|
| TXT |
- data - the string text record
|
| SRV |
- priority
- weight
- port
- target
|
| NS |
- data - a name server hostname
|
| CNAME |
- data - a canonical name record
|
| PTR |
|
| NAPTR |
- order
- preference
- flags
- service
- regexp
- replacement
|
|
| ips |
string hostString adr! deviceAddress |
Resolves a domain name to a list of IP addresses.
Returns a list of adr! objects with the addresses.
deviceAddress (optional) is the interface do request from.
This is the same thing as:
dns[hostString 'A' 1 deviceAddress].map[v.answer] & dns[hostString 'AAAA' 1 deviceAddress].map[v.answer]
|
| hosts |
adr! address adr! deviceAddress
|
Reverse resolves an ip address to a list of domain names.
Returns a list of hostnames corresponding to that address.
deviceAddress is optional.
This is the same thing as:
dns[address.str 'PTR' 1].map[v.answer]
|
| alternate |
adr! address string type
|
Returns an object that also has the ips and hosts , and can also be called as a function,
but calls the dns server specified by address with the protocol specified by type ('udp' or 'tcp'),
rather than your router's default dns server.
|
| servers |
|
A member that returns the list of DNS servers the operating system uses.
|
|
| socket |
Object for handling raw internet packets.
The behavior of this object can be overridden using the socketObject attribute.
Has the following method:
|
| make |
deviceAddress |
Initializes and returns a raw packet stream with the given destination.
Returns a chan object that receives IP packets of any version.
deviceAddress is the device to listen from and is optional (defaulting to adr.own[0]).
Outputting on out sends a packet, and new packets will come in on in (if its listening).
The in property always has the encoding "bits" .
On call of close the socket will be closed.
|
Objects returned by socket 's constructor have the following method:
| listen |
|
A pseudo-method that starts listening to the socket.
Can restart listening on a socket that was previously closed.
Returns the calling object (for chaining).
; processes all the packets that come through (infinite loop)
var server = socket[].listen
df server.in packet:
if packet.version == 4: ; ipv4
var p = ipv4[packet.data]
var ipv4Packet = ipv4[
ttl = 30
protocol = 17
destination = p.source
data = udp[dest=8000 data="hi"].serialize
]
server.out.=ins[ipv4Packet] ; sends the IPv4 packet with a UDP message
|
|
| ipv4 |
IPv4 packet object.
The behavior of this object can be overridden using the ipv4Object attribute.
Has the following methods:
| make |
ttl protocol destination data |
Creates an ipv4 packet.
|
| make |
bitList |
Creates an ipv4 packet from bits.
|
And has the following members:
| version |
The IP protocol version (4 for this).
|
| ttl |
Time to live.
|
| protocol |
Transport layer protocol number.
|
| destination |
An object containing an adr! object representing the address the packet was addressed to.
|
| bits |
Contains the packet's bits serialization.
|
|
| ipv6 |
Just like IPv4 except with IPv6 members instead.
The behavior of this object can be overridden using the ipv6Object attribute.
|
| url |
An object for parsing, creating, and handling URLs.
Quick reference: protocol:[//[user:pass@]host[:port]][/]path[?query.str][#frag]
url has the following methods:
| make |
urlString |
This constructor parses the urlString and returns a url object that represents it.
|
| make |
options |
Returns a url object that has the passed properties.
The properties can be any of
protocol , host , port , path ,
query , frag , fullHost , or fullPath
(host and port are mutually exlucive with fullHost, and path and query are mutually exclusive with fullPath).
|
| encode |
string |
URL-encode a string.
|
| decode |
string |
URL-decode a string.
|
| queryStr |
objectMap |
Returns a URL encoded query string that represents the object map.
Throws an exception if the object can't be represented by a query string.
|
url has the following member:
| defaults |
An object containing info on what port is default for a protocol.
Each key is a protocol, and each value is a port number.
|
url objects
url objects have the following members:
| protocol |
|
| user |
The username part of the URL.
|
| pass |
|
| host |
|
| port |
|
| path |
|
| query |
An object where each key is a url query property, and each value is its
value (either a string or a list of strings).
Also has the member:
| str |
Returns the url encoded query string.
Setting this variable changes the properties set in the urlObject.query object to match.
|
|
| frag |
Fragment identifier (the part after the # hash mark).
|
| fullhost |
host .cat[':'port ]
|
| fullPath |
path .cat[':'query.str ]
|
| str |
Returns the url as a string.
If the port is originally specified but is known to be the default (it checks url.defaults
for this), the port is known to be default for that protocol,
this omits the port in the string even if the port was originally specified.
Protocol and host is always converted to lower case.
|
The members protocol , host , port , path , query ,
frag , fullHost , or fullPath are all mutable so you can change the URL at will.
|
| http |
Object for handling HTTP requests.
The behavior of this object can be overridden using the httpObject attribute.
Has the following methods:
| listen |
port encodingName deviceAddress |
Listens for incomming http requests.
encodingName is optional, and defines how to decode the HTTP request and encode the response.
If encodingName is not defined, Lima will attempt to get the encoding from the request's "Content-Type" header,
using "bits" if none is found.
deviceAddress is optional.
Returns a list containing all the HTTP requests that will arrive on the HTTP socket
from the time listen was called to when the socket is closed.
Note that if you stop processing a request or close its connection before reading the entire
body or before reading request headers (etc), Lima should optimize that such that the connection
will be dropped or closed as soon as possible (potentially without loading anything but the status code).
|
| make |
host method resource query
headers body deviceAddress
|
Creates an http request object.
deviceAddress is optional.
query should be an object that will be built into a query string.
|
| cookie |
name value expire
|
Creates a cookie.
The following members can also be explicitly set: domain, maxAge, path, secure, httponly.
Note that maxAge will delete the 'expire' member if set (and vice versa).
|
var listener = http.listen[8080] ; connects to another computer
df listener.in request:
request.respond[503 nil "we're not on speaking terms"]
var messages = connection.split[''@] ; messages delimited by newlines
if messages[0] == 'hi':
connection.cat['hi'] ; send message back
else:
wout[messages[0]] ; print message
HTTP request objects
The objects returned by http 's constructor and http.listen
have the following members:
| method |
The http method.
|
| resource |
The requested resource.
|
| version |
The http version (eg. 1.1)
|
| source |
An object containing an adr! object representing the source address.
This is only populated on the computer receiving the request.
|
| open |
Contains true if the connection is open, false if closed.
|
| close |
Psuedo-member that closes the connection.
Does nothing if connection is already closed.
|
| query |
The query string. Also has the member:
| map |
The query parameter map (from parsing the query string).
Throws an exception if not able to parse the query string.
httpRequest.query.map.userId[0] ; get the first userid parameter in the query string
|
|
| headers |
A list of header values keyed by their name.
|
| body |
The http body (as a string).
Also has the following members:
| map |
The http body parsed as a parameter map ("post parameters").
Attempting to access map throws an exception if
the body is not able to parsed as a query string.
|
|
| str |
Returns the request as a string.
This can be used to send the packet out through a TCP socket.
|
HTTP request objects also have the following methods:
| respond |
status headers response keepalive |
Responds to the request.
Closes the connection after the request is sent unless keepalive is passed in as true .
Note that keepalive will keep the connection open forever until explicitly closed by one side or the other.
|
| send |
address sourcePort deviceAddress |
Sends the HTTP request to the given address and returns an HTTP response.
address can either be an adr! object,
or it can be a string host.
If address is a string, the host header will automatically be set
if it hasn't been already.
|
HTTP response objects
HTTP response object have the following members:
| status |
The http method.
|
| version |
The http version (eg. 1.1)
|
| open |
Contains true if the connection is open, false if closed.
|
| close |
Psuedo-member that closes the connection.
Does nothing if connection is already closed.
|
| headers |
A list of header values keyed by their name.
|
| body |
The http body (as a string).
Also has the following members:
| map |
The http body parsed as a parameter map ("post parameters").
Attempting to access map throws an exception if
the body is not able to parsed as a query string.
|
|
| str |
Returns the response as a string.
This can be used to send the packet out through a TCP socket.
|
|
| https |
Object for handling HTTPS requests. Has the same interface as http .
The behavior of this object can be overridden using the httpsObject attribute.
HTTPS request objects also have the following member:
| auth |
Returns the auth scheme used.
|
|
| lion |
Object for parsing and outputting LiON notation strings.
| [value] |
Returns a string representing the passed value. Only outputs normal properties of the object (not privileged members).
lion[5] ; outputs "5"
lion[{1 2 3}] ; outputs "{1 2 3}"
lion[{'a':3 'b':{'d':4}}] ; outputs "{a=3 b={d=4}}"
|
| parse[string] |
Returns the value represented by the LiON string.
lion.parse["5"] ; outputs 5
lion.parse["{1 2 3}"] ; outputs {1 2 3}
lion.parse["{a=3 b={d=4} c=~{"b" "d"}}"] ; outputs {'a':3 'b':{'d':4}}
|
|
| round |
value valueSet towardValue |
Returns the value rounded toward towardValue in the set valueSet .
valueSet is optional (defaulted to the set of integers - since numbers are what are usually rounded).
towardValue is optional, without it rounds to the nearest value in valueSet ,
with it rounds toward that towardValue .
Throws an exception if the value is not comparable with items in valueSet .
; all of these are true:
; numbers
round[34.245] == 34
round[34.5] == 35
round[34.8 int.set.map[v*.5]] == 35
round[34.7 int.set.map[v*.5]] == 34.5
round[34.7 int.set 0] == 34
round[34.7 int.set.map[[v*5]] 0] == 30
round[-34.7 int.set 0] == -34
round[34.7 int.set 0] == 34
round[34.7 int.set 00] == 35
round[34.7 int.set.map[[v*6]] 00] == 36 ; 6*6 == 36 and 6*5 == 30 (so 36 is closer)
round[-34.7 int.set -00] == -35
round[19.8 int.set 15] == 19
round[14.8 int.set 15] == 15]
; time
var t = time[2012-04-03 12:00]
; rounds to the nearest week (weeks here defined as starting on monday)
round[t time.tai.weeks] == time[2012-04-02]
; rounds to the nearest wednesday
round[t time.tai.weeks.map[v+dur[/0/2]]] == time[2012-04-04]
t.tai.round[t time.utc.years] == time[2012-01-01]
; midday counts as nearest to the next day (like .5 being rounded as closest to 1)
t.tai.round[t time.utc.days] == time[2012-04-04]
; rounds to the nearest day that is the 9th of a month
t.tai.round[t time.utc.months.map[v+dur[/0/9]]] == time[2012-04-09]
; rounds to the nearest 1/3 of a day (again rounding up in this case)
t.tai.round[t time.utc.days.map[v - dur[/1]/3]] == time[2012-04-05 16:00]
; rounds toward time 0 - in other words time[1/] or time[0001/01/01 00:00:00]
t.tai.round[t time.months dur.0.time] == time[2012-04-01]
; rounds toward time infinity (rounds up)
t.tai.round[t time.months dur.00.time] == time[2012-05-01]
|
| mag |
value |
Returns the magnitude of a value.
The value can be any number (int, real, complex, etc), or vector of numbers.
It should be noted that this function is equivalent to the absolute value when used on non-vector numbers.
|
| sir |
radians |
Sine for radians
|
| cor |
radians |
Cosine for radians
|
| deg |
|
Converts degrees to radians.
For example, [90] would be /2 (radians).
|
|
|
Gets the sign of a non-complex number. Returns either 1 or -1.
|
| .base |
value |
Gets the logarithm base `base` of the passed value.
|
| cat |
value1 value2 ... |
Shorthand for value1.cat[value2 value3 ...]
|
| toCodeString |
value |
Outputs a Lima code string representing that value.
|
| access |
get set |
Returns an accessor object.
The parameter get is the getter function for the value,
and set is the setter function.
All other operations on the object are performed on the result of
the getter function.
This is useful for defining getters and setters for an object.
var o = {
internalX = 5
x = access[
get=fn: return internalX
set=fn v: internalX = v
]
}
|
| load |
module type |
Loads a resource from a library. Usually this is loading a module (as a static or dynamic library).
module can either be a module name,
a module-path (explained below) to the file containing the module-file, or
a module-path with a version (eg<EMAIL_ADDRESS>.
type is optional (default='module') and describes whether to load the resource as a 'file'
or as a 'module'.
If type is "file" , it will return a file! object containing the file.
Otherwise, load will return a copy of the object representing the module.
A module-path is similar to how node.js require takes paths.
Module resolution is done as follows:
- If the path starts with "./" or "../", the file is looked for at the given path relative to the loading module
- If the path starts with "/", the file is looked for at the given absolute path
- Otherwise, a "modules" folder is looked for in the directory containing the loading module, and
- If found, the file is looked for at the path relative to that "modules" folder.
- If not found, step C is repeated for the parent directory
- If still not found, step C.1 is repeated until the current directory is the directory the package.lima file is in
- If it still isn't found, the first part of the path is looked for in the "dependencies" list in the "package.lima" file,
and searched for in the user's lima module installation directory.
- If found and the module-path is just the module's name, the file at the path listed
in the property "main" in that module's "package.lima" file, is loaded
- If found and the module-path is more than just the module's name,
the file at that path relative to that module's folder, is loaded
- If not found in the user's module installation directory and the name is listed in the package.lima file,
the program attempts to download the module and install it and its dependencies
in the user's module installation directory.
- If the module isn't found in any of the above ways, the path is appended with '.lima' and steps are tried again.
- If the module still isn't found,
load throws an exception
var websocket = load['websocket']
websocket.connect['ws://localhost'] ; calls the connect function from that library
; load non-lima modules
var crypto = load['./crypto-v1.0.0.lib'] ; loads crypto methods in the current scope
crypto.md5['something'] ; hypothetically in the crypto library
mix['customLibrary'] // you can also mix the module object directly into the current module
customLibraryFunction[45 'do something!'] ; hypothetically from the customLibrary module
; you can also specify a version
var markdownHelper =<EMAIL_ADDRESS>websocketHelper.parse['### hi']
Examples of types of packages you would use with this: lima source files (uncompiled libraries),
compiled static libraries, shared libraries (DLLs, so files), executables (loads it like a shared library).
If one of the filenames a string ending in ".lima", ".o", ".dll", ".so", etc,
it loads the code at the given path and uses the entire file as an object.
"package.lima" is a file, much like node.js's "package.json", containing information about the current package,
and the dependencies of that package. It can have the following properties (unless marked, a property is optional)
- name - (required) Must be a valid variable name
- version - (required)
- main - (required) The path (relative to the directory the package.lima file is in) to the main module file for this package.
- description
- keywords
- recommended - a list of optimization packages that will likely work well with the module.
- homepage
- bugs
- repository
- license
- author
- contributors
- ignore - files and folder to ignore when publishing the package
- dependencies - a list/object of modules a package depends on.
Each item in the list/object is one of the following:
<name>@<version> - downloads the specified <version> of the package <name> from lime (lima's package manager - like ruby gems, perl's cpan, or node's npm).
<version> should be a semvar version.
<name>: <url> - downloads the package <name> from the given url. It expects the downloaded file to be an archived tarball.
<name> - Downloads the latest version of the package <name> from lime.
After downloading, the package.lima file will be updated with the specific version downloaded
(turning it into the <name>@<version> format).
- private
- postinstall - a list of dependencies, how to check if they're installed, and how to install them if they're not.
Each item in the list should have the following format:
{ name = name ; name of the dependency
check = checkFunction ; returns true if the dependencies it is responsible for are met
build = buildFunction ; builds a dependency
}
If a build member isn't given for a particular dependency,
it indicates that the dependency can't be met automatically.
If a dependency isn't met and can't be met automatically,
an exception will be thrown containing a list of the names of the dependencies
that can't be fulfilled.
- location - a path to where external modules should be installed to.
Defaults to a standardized location depending on the operating system the program is running on.
Since "package.lima" is a full-fledge lima program, you can programatically define any of the above fields.
|
| dif |
originalObject newObject |
A function that compares the non-privileged members of two objects and returns the difference between them.
The return value is an object where each key is a property-path and each value describes the change.
The property-path is a list where [] indicates the object itself, ['a'] indicates
object.a , etc.
Each value has one of the following forms:
| {type='set'
val=value
}
|
This indicates that the property was set to a new value.
|
| {type='move'
from=originalIndex
to=newIndex
val=value
}
|
This indicates that an element in the list was moved from one starting index to another.
|
| {type='add'
index=index
val=value
}
|
This indicates that elements in the list were inserted at the given index.
|
| {type='remove'
index=index
val=value
}
|
This indicates that an element in the list was removed at the given index.
|
var a = {x={r=3}}
var b = {x={r=4}}
dif[a b] ; returns {['x','r']: {type='set' val=4}}
Note that iterating through this object will return changes in such a way that if they're applied one at a time
to the old object, you'll get the new object (with respect to non-privileged members of course).
|
|
Writes some information (by default to stdout).
wout[x] is short for atr.infoHandler[x] .
Can also print one argument without brackets, e.g. wout "hello"
|
|
condName:: testExpression
condition:
condition
:
[
]
|
is useful for making switch-statement-like constructs and more powerful
templatized conditionals.
The testExpression is evaluted for each of
the condition.
Has the implicit variable v that holds the value of the
condition being tested.
The implicit variable can be renamed using :: , which renames the condition parameter
to condName .
If the testExpression returns true, the
are executed and the is complete.
If not, the next set of condition are tested.
If there is an , the under it are
executed if none of the condition
yield a true result from the testExpression.
cond 50<v.height[]
personA: wout["The first"]
personB: wout["The second"]
else: wout["Nobody"]
; you can use else to attach fallback testFunctions
cond 50<v
34: doSomething[]
12: doSomethingElse[]
else: cond v%10 > 5
4: doSomething[]
else: doSomethingElse[]
; renaming the condition parameter
cond x:: x.has[4]
{1 2 4}: doSomething[]
See also: "Emulating switch statement fallthrough in Lima" under Concepts.
|
|
object value key count :
[
]
|
Lima's main loop construct (stands for "do for").
Iterates through each item object returned by object's IterList method.
value, key, and count will be automatically declared as var variables.
On each iteration, value contains the value of the next member in object,
key contains the key for that value, and
count contains a count of the members that have already been looped over.
Note that when using the default object iterator, the loop gets a reference-copy of the object
(a list of references to the object's members - like a list-slice)
that has references to the original object's members.
This means that removing, adding, or rearranging any elements in the original object
won't affect the progression or number of iterations of the loop.
count is optional, and key is optional if there is no count.
df called with no parameters (object, value, key, or count)
is an infinite loop.
|
Macro that jumps straight out of the loop, skipping any lines of code in the current loop's block.
If referenced from a pointer, break will jump to the break location for where it was referenced from:
df: ; infinite loop
var outerBreak = break~
var n=0
while true n+=1 :
if n > 5: outerBreak ; when n is 6 it will jump to after the infinite loop
break has one member:
| contin |
The continuation break will break to - ie the line right after the loop.
Just like break , if referenced from a pointer, continue will jump to the continue location for where it was referenced from.
|
|
|
Macro that skips the rest of the loop and continues with the next iteration of the loop.
continue has one member:
| contin |
The continuation continue will continue at
- ie getting the next value, key, and count of the object
before executing the statements again from the top.
|
|
|
|
:
[
]
|
A thread. Runs the asynchronously.
This is a similar concept to threads in a language like C or Java, or setTimeout in a language like Javascript.
Although a thread can't take parameters, it acts similar to a function in that it can ret urn a return-value
(accessible via the wait ).
thread s are primarily used to indicate outputs that can be reordered.
Normally, variable writes (including outputs) are treated as order-matters by default,
meaning that no reordering of variable writes can be done to optimize the program unless some of
those writes are in a different thread.
If order doesn't matter, threads should be used to indicate that.
Most of the time, threads should be very short, and executes some code that outputs something.
wout['hi'@] ; always written first
var data = getData[]
thread: wout[data] ; might be written second or third
wout['hello'@] ; also might be written second or third
creates a new scope.
A thread returns a future object (with properties wait and val )
that is resolved when the thread completes.
The object has an additional pseudo-method:
| weak[boolean] |
Makes the thread weak, meaning it isn't counted in the number of running threads.
boolean defaults to false, but if true sets a thread to be non-weak
(adding it back to the thread count if it isn't counted already).
|
The returned future also has a destructor that, when run, instructs any exceptions thrown from
the thread to be passed to the uncaughtExceptionHandler only if val or
wait have not been called.
This means that the uncaughtExceptionHandler will only be called for exceptions
that have no chance of being thrown elsewhere.
The statements that run in a thread are all run atomically, meaning that conceptually,
once the thread starts running, nothing else is run concurrently.
This allows programs to have much more deterministic robust behavior,
avoiding an infinity of race conditions that can occur in true concurrent code.
As long as two threads don't have conflicting code (eg writes to share variables), the
compiler may optimize the code to run truely concurrently.
Note that the main thread is *not* run atomically, and so if a thread may modify a variable
while it is in an inconsistent state in the main thread, an atomic block should be used
in the main thread.
| thread.count |
The number of non-weak threads that are currently running.
This number goes to 0 when the main thread is complete and the program should exit soon.
This can be used to do things like exit a thread early
(like setTimeout.unref in node.js).
|
|
|
:
parameters
unmake:
[:
parameters
unmake:
]
|
Object constructor.
Implicitly creates a copy of the this , runs the statements in make for that new object,
and also returns it implicitly.
var x = {
var[a b]
make self.a x:
b = 5*x
}
x[1 2] ; returns a copy of x that looks like {a=1 b=10 fn! make}
The method, however, may explictly return, in which case it returns that alternate object instead
(and the implicitly created object is discarded - or really [under the hood] is never created in the first place).
var x = {
make a x:
ret {a=a x=x}
}
x[1 2] ; returns {a=1 b=2}
make overrides the bracket operator for the object it is written,
but any object created via the constructor does *not* have the constructor and retains
whatever other bracket operator may be defined.
var x = {
operator[ [ ] = fn val:
ret val+1
make:
ret {a=3 b=5}
}
var newX = x[1 2] ; returns {a=3 b=5}
newX[99] ; returns 100
The statements under the unmake section describe the destructor/finalizer.
This is code that run when one of the following happens:
- the is explicitly deleted (by setting the variable holding it to nil)
- the object goes out of scope and it has no strong-references pointed to it
(note that closures may implicitly have strong-references to the object)
The unmake of the object returned from make is guaranteed to be run (unlike in languages like python)
as long as the program isn't forcibly killed.
Destructors are called top-down if possible, meaning that objects that no other non-destructed
object is pointing to will be destructed one by one until none are left.
In the case of circular references, destructors are called in the order the objects were created.
To control the order of destruction, an object can explicitly be destructed by setting it to nil .
An exception is thrown if the object is resurrected and any new strong-reference to the object is set to nil .
After unmake runs, any weak-references will be pointing to nil .
By default, if the environment compiled to supports garbage collection,
the unmake block will be run some time after the object becomes inaccessible (and before the end of the program).
If you want to change that behavior, use an assert to specify tighter constraints, for example:
var x = {
make:
this.connection = getConnection[]
unmake:
var t = unixtime.after:
this.connection.close[]
assert(meta[this].detachmentTime-t < 5) ; the destructor must be called within 5 seconds of the object becomming inaccessible
}
var newX = x[1 2] ; returns {a=3 b=5}
newX[99] ; returns 100
Constraining the time the destructor is called can prevent some potential garbage collection optimization, but can
guarantee more deterministic behavior.
|
|
[:
parameters
]
|
Overloads operators ==, !=, <, >, <=, and >= all at the same time.
The should only have one parameter (the parameter to compare the object to).
Should return true if the value is greater than the object, and false otherwise.
It will infer everything else from this return value.
Note that this implicitly creates overloaded operators in the same scope as the object the is defined in,
and Lima will complain if the creates a overloaded operator whos parameters conflict with an existing operator.
|
|
Makes a variable not accessible outside its .
Private members of compiled files are not externally available
(when statically or dynamically linking to it), while public members are.
|
|
Used on functions, causes the closure (the function) to immediately bind its upvalues
(the variables outside that function's immediate inner scope),
so that changes to the upvalues aren't visible to the function itself.
This does something similar to what a do-statement does in coffeescript.
Note that it is possible to finalize and variables accessed via .
; how a normal closure operates
var a = 'hi'
var f = fn:
wout[a]
a = 'no'
f[] ; prints 'no'
; how final changes things:
var b = 'hi'
var f2 = final fn:
wout[b]
b = 'no'
f[] ; prints 'hi'
; that last final function is equaivalent to this:
var b = 'hi'
const var constB = b ; copy b to another variable
var f2 = final fn:
wout[constB] ; use that copy instead
b = 'no' ; changing the original obviously doesn't affect the copy
f[] ; prints 'hi'
Bracket Operator:
|
variableName variableNames |
Returns an attribute like basic that binds only the variables whos names are given in the parameter list.
var[a=5 b=6]
var f = final[a] fn:
wout[a' and 'b]
a = 10
b = 11
f[] ; prints '5 and 11' because a is bound early, but b is left as a free variable
Note that since the bracket operator is defined for ,
brackets cannot be used to set this attribute over multiple statements (and so must be written individually).
|
|
|
[
; object-inheritance statement
:[
; member statement
memberName : memberAlias
member statements
]
; object-inheritance statement
object2 :[!
memberName : memberAlias
memberNames
]
object-inheritance statements
]
:
:
memberName : memberAlias
member statements
object2 :!
memberName : memberAlias
memberNames
object-inheritance statements
|
Makes the privileged members of a previously defined object (or ) available for
use in the object (or ) being defined,
somewhat similar to extends in java.
It has mixin-like semantics: the object gets a copy of the private and public
privileged functions and variables of each object passed in the parameters.
An object with multiple inheritance involving name collisions must explicitly
declare which object it will get its member from.
- should be a lima expression that returns an object to inherit from.
- Everything inside the normal colon section (
:[ ] or just v: ),
including the colon-bracket, is optional.
Indicates that only the listed members will be inherited from the .
- Everything inside the not-colon section (
:[! ] or just v:! ),
including the not-colon-bracket, is optional.
Indicates that all members of the will be inherited,
except for the ones listed without a memberAlias.
Any member *with* a memberAlias will be included as that alias.
- For a given object-inheritance statement, there may be up to one colon-bracket operator
(either the normal colon-bracket, the non-colon-bracket operator, or neither).
- memberName is the name of the member to inherit
(or not inherit in the case of the not-colon-bracket operator).
This can use the special
operator construct (operator [op])
to indicate the symbols for an operator
(whether refering to the original symbol, or aliasing an operator with a different symbol).
- memberAlias is a name in which the calling object will inherit
the member given by memberName.
These are optional, defaulting to calling the member by its original name.
With the colon-not operator, any members that are aliased are *included* with that alias (not excluded).
- memberName and memberAlias can use dot syntax to describe
how sub-members should be inherited.
mix:
someObject: a.x ; inherits someObject.a.x (but not any of a's other members)
mix:
someObject:! b.x ; inherits all of someObject, including b, except b.x
mix:
someObject:! c.x : c.theXX ; inherits all of someObject except c.x, including c,
; and then aliases c.x as c.theXX
Note that colon and colon-bracket syntax can be mixed and matched.
You can use the result of an expression for memberAlias in this macro by
prefixing the expression with the @ sign.
mix[
redrobin ; all members of redrobin are inherited
someObj2:[! drogon puff.p ] ; all members are inherited except drogon and puff.p
someObj3:[! dang yucky ] ; all members are inherited except dang and yucky
someObj4: ; members inherited under aliases
a : memberA
b : @"memberB" ; using the result of a string as the alias name
d.x : f.z ; aliasing a submember onto another member entirely (f)
e.x : @"e.xx" ; string member names for submembers use dot syntax
someObj5:!
operator[<] ; inherits all members except the < operator
garth : garthAgain ; and someObj5.garth is inherited under the alias garthAgain
someObj5:
operator[<] : operator[<<<] ; inherits an operator as a different operator symbol
; inherit members of other modules
load["/C/src/lib/aFile2.lima"] ; absolute path
load["../lib/aFile.o"] ; path relative to the current file
load["../lib/aFile3.lima"]:[specialMethod fancyMethod]
]
Aliasing and selective inheritance is used to make inheritance and
module inclusions completely unambiguous.
Since modules are themselves objects,
(in conjunction with load ) can be used like
#include /import in C/Java respectively.
nests.
This means that if you objectA into objectB, and objectB into objectC,
members from objectA will be available in objectC.
When you want to allow objectB to use objectA's members without those members also ending up in objectC
(for example when mix ing a module as a dependency),
simply use private :
private mix:
load["dependency1.lima"]
|
| use[packagePath packagePath2 ...] |
A convenience function that allows dry module loading.
Each packagePath is a string containing the path to a module package to load.
Each passed module module path will automatically set a variable with the same file name
as the value passed in (minus the extension).
The following are equivalent:
<EMAIL_ADDRESS>'../y/someModule']
var[<EMAIL_ADDRESS>someModule=load['../y/someModule.lima'] ]
|
| with variable1 variable2 ...:
|
A with block creates a new isolated scope that only passes through the ability to
access the specified variables.
The return value of the statements are returned from the with call.
Allows a programmer to easily section off blocks of code that are relatively independent,
which can make it a lot easier to refactor later, especially if you want to turn those blocks into separate functions.
var x=1, y=2, z=3
var result = with x y:
var c = 3
wout[x] ; works fine
wout[z] ; doesn't work
return x+y+c
wout[c] ; doesn't work because the c defined inside the with block isn't in scope
Lima's with block is what Jai calls a "capture".
|
-
In some languages, you have special syntax for accessing items from the end of an array like this:
someList[-4] .
This would access the 4th to last element of the array.
Lima doesn't have that syntax (since any value is already a valid key to use in an object),
but you can do the same thing in one of two ways:
{1 2 3 4 5}.sort[-v][3] ; sort the list in revrse, then select the the 4th element (the 4th to last element of the original list - 2)
x = {1 2 3 4 5} ; ..
x[x.len-4] ; another way to access the 4th to last element
Because lima should optimize this, using sort should be just as good as using the length.
-
x.group[v].keys
-
To emulate a do-while, you can have an infinite loop that has a break condition at the end of it:
while 1: ; the 'do' of C's do-while
wout "I'm finally in the loop!"@
; ....
if someCondition: break ; the 'while' of the do while
-
var x = {1 2 3}
x.sort[-k] ; sort by the negative of the index (key)
; returns {3 2 1}
var y = {1 5 2 5 93 0 -4}
y.sort[-v] ; sorts on the negative of the value
; returns {93 5 5 2 1 0 -4}
y.sort[[ !(v<v2) ]] ; the complement of any expression in sort[[ ]] is a reverse sort
; also returns {93 5 5 2 1 0 -4}
-
Because lima will optimize away most unneccessary processing, finding a minimum can use the first value returned from a sort.
It might seem nice to have a
minimum function, but what happens when you need to find the 4th lowest value in a list?
Or if you need all 4 lowest values?
Using sort gives a good generalized way of finding this type of thing:
var x = {5 2 87 9 6 8 4}
x.sort[v][0] ; sort by value and get the first element - the minimum
x.sort[v][3] ; find the fourth lowest value
x.sort[v][0..3]] ; get the 4 lowest values
x.sort[-v][0] ; find the maximum
-
Lima doesn't have a `static` keyword like C++ does, but it can still do the same thing.
You just need to use a reference:
var class = {
ref myStaticProperty ~> 0
make:
myStaticProperty += 1
}
var newInstance1 = class[]
wout[class.myStaticProperty] ; prints 1
wout[newInstance1.myStaticProperty] ; prints 1
var newInstance2 = class[]
wout[class.myStaticProperty] ; prints 2
wout[newInstance1.myStaticProperty] ; prints 2
wout[newInstance2.myStaticProperty] ; prints 2
class.myStaticProperty = 3
wout[class.myStaticProperty] ; prints 3
wout[newInstance1.myStaticProperty] ; prints 3
wout[newInstance2.myStaticProperty] ; prints 3
newInstance1.myStaticProperty = 4
wout[class.myStaticProperty] ; prints 4
wout[newInstance1.myStaticProperty] ; prints 4
wout[newInstance2.myStaticProperty] ; prints 4
In the above case, its pointing to an immediate value, so changing it isn't a problem.
But if you want to create a static variable initialized to a copy of a variable rather than referencing that variable,
like static members in C++, just create a copy first:
var x = 5
var class = {
static: var internalX = x
ref myStaticProperty ~> internalX
make input:
myStaticProperty += 11
}
var newInstance1 = class[]
wout[class.myStaticProperty] ; prints 16
wout[newInstance1.myStaticProperty] ; prints 16
wout[x] ; prints 5
Note that there's no equivalent of a static function variable.
This differs from many languages that allow static function variables.
To do this in Lima, you just need to use a variable in a higher scope.
-
If you select a slice of a list or object, the keys are preserved.
In such cases, you might want to rekey the array, so that all its members are elements.
"hello"[[k>2]] ; returns the object {3:'l' 4:'o'}
"hello"[[k>2]].sort[k] ; returns the object {0:'l' 2:'o'}, or equivalently "lo"
-
You can emulate classical string join (implode in php) using the psuedo-method
ins and the join method:
var L2 = {1 2 3 4 5}.ins[[', ']] ; this returns the list {1 ', ' 2 ', ' 3 ', ' 4 ', ' 5}
wout[L2.join[a.cat[b]]] ; this will print the list with commas
-
var x = 5 ; x1
var y = { ; y1
x1~>self.x
x = 10 ; x2
y = fn a: ; y2 (naming the parameter ''x instead of 'a' is illegal here)
var y = { ; y3
x2~>self.x
x=10 ; x3
y=fn b: ; y4 (naming the parameter 'x' instead of 'b' is illegal here)
self.x ; references x3
x1 ; references x1
; trying to access 'x' here is illegal
}
}
-
The trick is to make a reference to the object itself
x =
{ mythis ~> this ; mythis points to this
myself ~> self ; myself points to self
a=5
b=23
c=89
obj=
{ a = 'hi'
b = a ; b gets the value 'hi'
func = fn
[ var b=34 ; errors, becuase b is already declared in the current object
var c="nope"; errors, because c is already declared in a containing object
wout a ; errors, because a is an ambiguous name
wout self.a ; prints "hi"
wout self.b ; prints "hi" again
wout mythis.a ; prints 5
wout c ; prints 89
wout mythis.c ; also prints 89
wout myself.c ; prints 89 again
]
}
}
-
-
x =
{ a = 5
t = fn
[ ret 'x'
]
}
y =
{ private mix x:! t ; using all x's members except t
t = fn ; override t
[ ret oldt[].cat['y']
]
private mix x[t : oldt] ; using x's t member aliased as private member oldt
}
y.t ; returns 'xy'
y.oldt[] ; returns 'x'
-
Lima doesn't quite have the same kind of generics that a language like Java has,
because values in Lima don't have a "type" (instead a given type-set can either include a value or not).
However, you can still do most of the things you would want generics for using variables containing types:
var Statistics = fn type T:
ret {
list[T] percentiles ; 0, 20, 40, 60, 80, 100
T mean
T median
}
var x = Statistics[int] ; returns a statistics object where the type T is 'int'
In Lima, lists of a subclass can be passed anywhere a list of a superclass is required - ie
covariencea> is assumed.
This isn't true in, for example, java. Here's an example in Lima:
var Sublist = {
mix[list]
first = fn:
ret alist[0]
}
var see = fn list[int] alist:
wout[list]
Sublist[int] x = {1 2 3}
see[x] // is callable even tho the parameter is required to be a 'list[int]'
-
Generators and custom iterators aren't neccessary in lima, because optimizations like lazy list building (infinite or finite lists)
and futures/promises, take the burden off the programmer, and puts it on the compiler/interpreter.
Things done with coroutines should be done using normal list semantics (which makes code more consistent and allows the list results from these to be operated on by any function that operates on a list).
Generators, custom iterators, and corountines can all be implemented either using function immediates, list semantics, or threads.
For example, a custom iterator:
var threeTimes = fn f:
df 13:
f[]
threeTimes[fn:
wout 'cry'@
]
or a generator:
createInfiniteList = fn:
var theList = {}
var next = 1;
while true:
infiniteList.=ins[next]
next++
ret theList
infiniteList = infiniteList[]
wout[infiniteList[3]] ; prints 4
Lima doesn't need semantics like yield to impliment coroutines.
In ECMAScript 4:
function fringe (tree) {
if (tree is like {left:*, right:*}) {
for (let leaf.in fringe(tree.left))
yield leaf
for (let leaf.in fringe(tree.right))
yield leaf
} else {
yield tree
}
}
//; tree
var tree = { left: { left: 37, right: 42 }, right: "foo" }
for ( let x.in fringe(tree) ) { //; Iterate over the tree
trace(x); //; 37, 42, "foo"
}
The same can be done much more simply in lima like this:
fringe = fn tree: ; returns a (possibly lazily constructed) list
if like[tree {var[left right]}]:
ret fringe[tree.left].cat[fringe[tree.right]]
else:
ret {tree}
var tree = {left = {left=37 right=42} right='foo'}
df fringe[tree] : x :
wout x
-
chooseset[int string fn!] ; maybe this is wrong?
-
Tho there would probably be no reason to ever create a linked list in Lima,
it's informative to show how one of the most common interview questions is done:
// head node of a linked list
node = {
ref? next
}
reverse = fn node! head:
ref? previous ~> nil
while head !~> nil:
{head.next previous head} ~> {previous head head.next}
head = previous
main:
var head = {"A" next~>{"B" next~>{"C" next~>{"D"}}}}
reverse[head]
-
aUnion =
{ chooseset[real int string] value ; using a set of types as a tuple type
acc f ; real accessor
[ ret ref[value]
]
acc i
[ ret self.f ; int accessor
]
acc s
[ ret self.f ; string accessor
]
}
-
Since anything in Lima can potentially return a macro, it may be confusing to think about how operator precendence interacts
in exprssions where macros may be returned from one part of the expression.
Operator precedences are resolved normally, and when one resolves to a macro the remaining expression is passed to the macro,
which may consume only part of that remaining expression.
Once the macro resolves, expression resolution continues as normal.
An example:
; Assume that operator precedence is the same as normal arithmetic and
; assume that b*1 resolves to b, and a+b returns a macro
a + b*1 + 4*5
; will resolve to
a + b + 4*5 ; which resolves to..
someMacro + 4*5 ; which resolves to..
; if someMacro only consume "+ 4", and returns 40 this will then resolve to:
40*5
-
You can use
arb to implement don't cares in an if statment:
if: a==5 $ arb[{true false}]
; do something
This means that it will always ; do something if a equals 5, but if a does not equal 5,
then the compiler can choose whether or not to execute that code (if it somehow simplifies the logic of the program)
-
-
In SQL:
select productid, sum(unitprice * quantity) as total
from o in db.orders
inner join rd in db.orderdetails
on o.orderid == rd.orderid
where o.zipcode == 94503
group by rd.productid
and in Lima:
db.orders.map[ {mix[ v db.orderdetails[[o: o.orderid == v.orderid]][0] ]} ] ; join
[[ v.zipcode == 94503 ]] ; where
.map[ {productId=v.productId price=v.unitprice*v.quantity][0].rd ;
.group[v.productId].map[{ v[0].productId v.join[a.price+b.price] }]
-
In XQuery:
for $b in $bs/book
return
<result>
{$b/title}
{$b/author}
</result>
And in Lima:
bs.book[[ v[{'title' 'author'}.has[k]] ]] ; one way
bs.book[[ {title=v.title author=v.author} ]] ; another way
-
ref.fn theFunc
anonFuncCreator = fn list[string string] characters: ; takes in a mapping from character to character
theFunc ~> fn string a: ; create anonymous function
var charactersCopy = characters
ref.(list[string string]) map ~> charactersCopy
if {a} < map.keys: ; if map has the key a
ret map[a]
else:
ret a ; returns input character if there is no mapping
-
In hardware design, a wire is what connects an input of a device to the output of another device.
In Lima, one can describe the same functionality as a wire using functions:
main = fn:
int[ a=2 b=5 c=13]
fn! aWire = fn[ ret c+b*a ] ; acts like a wire
wout aWire[] ; will print '23' (13+5*2)
a+=4
wout aWire[] ; will print '43' (13+5*6)
-
There are two ways to do event-driven-like programming:
- list iteration and slicing
- variable observation
In traditional programming langauges, programming with events is done by setting up callback handlers that
are called when an event happens, for example in javascript:
emitter.on('someEvent', function(eventData) {
doSomethingWith(eventData)
})
...
emtter.emit('someEvent', {your:'data'})
But in Lima, we can do this kind of thing with standard lists and the tslice function:
var events = []
; this event isn't printed because it happens before tslice was called
events.=cat['1']
var stop = future
var relevantEvents = events.tslice[stop]
events.=cat['2']
df relevantEvents event: ; this loop prints '23'
wout[event]
events.=cat['3']
stop.done
; this event isn't printed because it happens after the 'stop' future is resolved
events.=cat['4']
Another way to do event handling is by doing variable observation using the 'change' function:
var events = []
; this event isn't printed because it happens before the change handler is set up
events.=cat['1']
var c = change[events:oldEvents]:
var d = dif[oldEvents events]
df d.elements.add event: ; assume all changes are appends
wout[event] ; this loop prints '2'
events.=cat['2']
c.stop
; this event isn't printed because it happens after the 'change' handler is stopped
events.=cat['3']
-
In Lima, circular dependencies can not only happen for module dependency loading, but can also happen because
of dependencies between variables in the code.
For example:
var x = 5
var a = {1 2 3}
var slice = v.tslice[process.end]
df slice v:
if v == 4:
x = 10
if x > 6:
a.=cat[4]
The above code can never finish because the if statement doesn't know the value of x.
The loop is also waiting to see if a will have a 4 appended to it,
which it might if x becomes more than 6 because of the loop.
This behavior should throw an exception if detected.
-
change[a:oldValue]:
if oldValue == 0 && a==1: ; reacts to a positive edge: 0 to 1
; do something
oldValue == 1 && a==0: ; reacts to a negative edge: 1 to 0
; do something
-
var o = {1 3 9 12 15}
var o2 = {var[x=90]}
o.keys >= {2} ; true (has the value 9 at index 2)
o.keys >= {12} ; false
o2.keys >= 'x' ; true since o2 has a defined member x
-
Testing set comparisons between two lists can be done in multiple ways.
For consistency, only one of those ways is preferred and the compiler will correct code that uses the un-preferred comparisons.
How to test if an item is contained in an object/list
How to test if a list is an improper subset of another
- Preferred:
list1 <= list2 asks "Is list1 a subset of list2 or equals list2?".
list1[[ list2[[v2:v == v2]].join[a$b] ]].join[a&b] asks "Does every member in list1 equal some item in list2?"
How to test if a list is a proper subset of another
- Preferred:
list1 < list2 asks "Is list1 a proper subset of list2".
list1[[ list2[[v2: v==v2]].join[a$b] ]].join[a&b] & list2[[list1[[v1: v!=v1]].join[a$b] ]].join[a&b] == no asks "Does every member in list1 equal some item in list2? And is there some item in list2 that doesn't equal any item in list1?"
list1[[ list2[[v2: v==v2]].join[a$b] ]].join[a&b] & list1!<>list2 asks "Does every member in list1 equal some item in list2? And does list1 not contain all the members that list2 contains?"
-
- Preferred:
var conditions = fn k: (200285).has[k]
var charactersToCut = aList[[conditions[k]]]
aList.rm[[conditions[k]]]
After this code is run, aList 's elements with keys larger than 285 are shifted "left"
so that an originally having the key 286 will now have the key 200, etc.
This method works on members that are not elements, but no shifting happens.
var charactersToCut = aList[[(200285).has[k]]]
aList = aList[ !({k} < charactersToCut.keys) ].sort[k] ; get the list without the cut characters and rekey them (via sort)
var charactersToCut = aList[[(200285).has[k]]]
df charactersToCut v k: aList[k] = nil ; delete all the members that were cut
-
Files are always opened as binary in Lima.
Text-mode, in a language like C, encodes newlines in a platform specific on certain platforms.
In reality, this is just another encoding, and Lima treats it that way.
If you need to handle operating-system specific newlines, you should open a file with the appropriate
decoder that handles those OS-specific newlines (e.g.
dosText ).
-
The first means "Does every member in list equal some member of list2?",
while the second means "Does any member in list2 equal every member of list2?".
Example,
{1 2 3}[[ {4 5 6}[[v2: v == v2]][$] ]][&] ; is the same as:
{4 5 6}[[v2: 1 == v2]][$] & {4 5 6}[[v2: 2 == v2]][$] & {4 5 6}[[v2: 3 == v2]][$] ; which is the same as:
(1==4 $ 1==5 $ 1==6) & (2==4 $ 2==5 $ 2==6) & (3==4 $ 3==5 $ 3==6)
;while
{4 5 6}[[ {1 2 3}[[v2: v == v2]][&] ]][$] ; is the same as:
{1 2 3}[[v2: 1 == v2]][&] $ {1 2 3}[[v2: 2 == v2]][&] $ {1 2 3}[[v2: 3 == v2]][&] ; which is the same as:
(4==1 & 4==2 & 4==3) $ (5==1 & 5==2 & 5==3) $ (6==1 & 6==2 & 6==3)
-
-
list[[v==value]].len counts the number of members that equal value
-
list[[v==value]].keys[0] finds the index of the first member of the list that equals value
-
var x = 5.23456
var floor = x//1 ; exactly like floor
var ceiling = x//1+1 ; exactly like ceiling
-
The Haskal list comprehension syntax:
s = [ 2*x | x <- [0100], x^2 > 3 ]
and the equivalent python list comprehension syntax:
s = [2*x for x.in range(100) if x**2 > 3]
can be done in Lima by doing:
s = (0..100)[0 100][v^2 > 3][[v*2]]
-
The filter function in python and other languages creates a new list of items from the items in a list that cause some function to return true.
For example, "filter(list, GT4)" might return a list of elements that are greater than 4.
In lima this can be done like this:
list[[v>4]]
-
To select a list of second-dimension items from a multi-dimensional list, you'd use the map method. E.g.:
var rowFirstMatrix = {
{1 2 3}
{4 5 6}
{7 8 9}
}
rowFirstMatrix[2] ; selects the second row: {4 5 6}
rowFirstMatrix.map[v[2]] ; selects the second column: {2 5 8}
-
Lima doesn't have an enum construct, but you can still do enums using a pattern some lazy programers already use: returning strings.
In most languages, the IDE and compiler don't have any insight into the values a function can return, but in Lima they do.
Using simple strings as enums gives the benefits of not having to explicitly enumerate all possible values
(duplicating the values you would already be using).
var colors = fn int x:
if x == ?0::
1: ret 'blue'
2: ret 'green'
3: ret 'purple'
var color = colors[1];
if
color == 'green':
wout['GREEN!']
color == 'purpel':
wout['PURPLE!'] ; IDE/compiler/interpreter should tell you this code path can never happen because the function will never return that misspelling 'purpel'
-
Lima will attempt to find any variables that are un-initialized upon first read access.
When a variable has a particular spot in your program that it is set, it is best to let it be set there, rather than setting it in both there and at the declaration.
The reason for this is that you can find problems earlier if you don't build redundancies into your program.
A redundancy can make your program work the first iteration of a process, but then it will fail the second time (or later) - which is a much more mysterious problem to debug.
-
Lima doesn't have traditional inheritance.
Lima instead takes the approach of languages like lua, lisp, and Ruby in providing "mixins" or "traits" rather than parent-child inheritance.
Lima's form of inheritance is what's formally referred to as 'traits' (tho unlike tranditional traits,
objects can inherit state) and are similar to mixins.
Members from one object can be mix ed into another,
but there is no sub-class/super-class relationship, and no "is-a".
Any conflicting members must be explicitly disambiguated (by renaming one or both members, or excluding one or both members).
A consequence of this is that there is no automatic solution to the diamond problem, and solving any diamond problem is left to the programmer
(tho the compiler or interpreter will not fail silently but will force a solution to be made).
Lima opts to not have inheritance for the same reason Go opts not to (http://golang.org/doc/go_faq.html#inheritance),
inheritance is too complicated and can cause insidious bugs.
However, Lima's trait-like system gives the programmer the flexibility of multiple inheritance, with the safety of single.
-
Lima doesn't offer an atexit function.
This is because atexit functions are prone to cause scattered code.
Instead, if you want to do something like this, create a function that does exit code.
Whenever you need to For example:
fn exitFunc
[ 5+4
doSomeFunction[ ]
obj.save[ ]
; etc
exit
]
You can enforce that this function is used in place of exit by using the exitFn attribute.
Also, object desctructors could cover the use cases you would use atexit for.
Or possibly you could use a try-finally block for this as well.
-
Lima was built with the intention of being fully automatically opimized,
and therefore doesn't have any specification of concepts like tail-calls that affect the speed of a program, but not the output.
In short, any reasonable Lima implementation should implement proper tail calls, but it isn't part of the language.
-
Lima doesn't do short circuit evaluation by default
(tho it may optimize certain boolean operations such that they do short-circuit evaluation under the hood).
So how do you do it when you want it?
;In java, you might want to do things like this every once in a while:
if( y != 0 && 5/y > 1
|| x.equals("yes") && testFunctionWithSideEffects()
|| z != null && z.isTrue
|| w != null && w.method()
) {
doSomething();
}
; In lima:
if if[y!=0: 5/y>1 else: false] ; use if's return value
$ if[x=='yes': testFunctionWithSideEffects[] else: false]
$ z?.isTrue?[] == true ; use the safe-traversal operator
$ z?.method?[] == true ; have to check if its true because it could be nil
-
String interpolation is really just another way to concatenate variables with strings
On the surface, string interpolation seems like a nice feature that makes strings more concise.
The downsides, however, far outweigh that small benefit.
I'm lumping using special characters (like "/n" and "/t" etc) under the umbrella of string interpolation.
Increase cognitive load.
Really interpolation is its own little mini language that does the exact same thing as the main language.
Just using concatenation doesn't require extra knowlege.
Writing cat["x is "x", f(x) is "f[x]] in perl is "x is $x, f(x) is @{[&f(x)]}" - yuck.
And it is not only one-time knowlege, it requires constant vigilence.
If you want to type literal strings, you need to make sure you escape anything used as special
characters or interpreted in any way other than literally.
- Its not that much shorter than lima:
; ruby
"the meaning of life is #{11+31}!"
"#{a} and #{b} and #{c} and #{d}"
; lima
"the meaning of life is ",(11+31),"!"
cat["the meaning of life is "11+31"!"]
cat[a" and "b" and "c" and "d] ; oops this is actually shorter in lima!
; perl - perl's a little shorter tho
"$a and $b and $c and $d"
- Complicates escaping in pasted text and generated code.
Lima's strings can contain any pasted text as long as quotes are escaped.
Contrast that with ruby where not only do you have to escape any instance of "${" and "\"
as well.
- Many implementations of string interpolation are templates that are then filled in by values.
An example of this is printr in C.
The problem with this implementation is it visually separates the variables from the string,
making it harder to read.
Some may argue this separates presentation from data, but thats not correct.
It only separates related code, which isn't good.
; for example
templateA = fn int[a b]:
ret ''.cat[a" and "b]
; the following is much harder to read
templateB = fn int[a b]:
ret "%s and %s".interpolate[a b] ; hypothetical interpolation
Embedding code in strings solves a non-existant problem.
If you want to embed expressions inside strings, ask yourself, why you aren't using variables?
-
Lima's
change and dif functions allows you to easily observe objects in an event-driven way
var x = 3
change[x]:
wout['x changed to:'x]
x = 4 ; 'x changed to : 4' is printed
var y = {1 2 3}
change[y:oldy]:
var d = dif[oldy y]
if d.set != nil:
wout['y was set to a different object']
df d.elements v:
if v.type == 'add':
wout['y got a new element: 'v.val]
v.type === 'move':
wout['y got shuffled around']
v.type === 'remove':
wout['y lost an element to the war :(']
-
The attributes and allow a programmer to specify how
external output may be grabbed before it's used.
But for statements without this attributes, the optimizer can choose whether to grab a file lazily or not
(it might not if it thinks starting loading some output will lower latency later) and
there is no attribute to specify explicitly lazy data grabbing.
So while often things are lazily loaded by default, if you want to ensure something is lazily loaded,
you should include optimizers (or write your own) that make that determination.
-
A useful use of macro functionality is to modify normal syntax to mean something not-so-normal.
For example, a macro could be defined such that this:
atomicAssignment [
a = getFromServer['a']
b = getFromServer['b']
c = getFromServer['c']
d = getFromServer['d']
e = getFromServer['e']
]
is transformed into:
var temp_a = getFromServer['a']
var temp_b = getFromServer['b']
var temp_c = getFromServer['c']
var temp_d = getFromServer['d']
var temp_e = getFromServer['e']
atomic
statements1: a = temp_a
statements2: b = temp_b
statements3: c = temp_c
statements4: d = temp_d
statements5: e = temp_e
]
This would have the benefit of being much easier to write, while also allowing all those variables to be swiched at exactly the same time.
This could be useful, for example, for programming a caching mechanism that refreshes periodically.
-
The placement of the * and & symbols in C are not very intuitive,
and force the reader to mentally parse a group of very similar looking syntax into very different semantics
(values vs addresses).
Lima's pointer syntax aims to reduce this cognitive load.
As an (imperfect) quantatitive measure, we can count the number of "weird" symbols in this set of statements:
; C Lima
int a=5 int a = 5
int *p, *p2 ref.int[p p2]
int** p3, **p4 ref.ref.int[p3 p4]
p = &a p ~> a
p2 = p p2 ~> p
p3 = &p p3 ~> p~
*p = a p = a
*p = *p2 p = p2
*p = **p3 p = p3
p = *p3 p ~> p3
a = **p3 a = p3
p4 = p3 p4~ = p3~
*p4 = *p3 p4 ~> p3~~
**p4 = **p3 p4 = p3
C has a count of 22 "weird" symbols (consisting of the structures: '*' and '&'),
whereas Lima has a count of 13 weird symbols (consisting of the structures: 'ref', '~', and '~>').
This significantly lower number indicates that the reader doesn't have to do as much mental work to understand
the statements.
Lima's reference syntax is easier than C's when pointers are being pointed to normal values
or are being used as the values they reference, but is harder when pointers are being pointed at other
pointers, especially for higher level pointers (a pointer to a pointer to a pointer... etc).
The argument here is that pointers are more often pointed at normal values and being used as those normal values,
than being pointed at eachother.
And higher level pointers are very rare.
-
var sparseMatrix = {
private var internal = {}
access
get else index:
ret interal[index] | 0
set else index value:
internal[index] = value
}
-
Warp the objects in your scene if you want to change the aspect ratio of the whole scene.
-
In languages like C, a switch statement allows what's known as "fallthrough" where the statements under a 'case' continue onto
the statements in the next 'case' unless you 'break' out of that case.
Here's an example in C:
switch( someValue ) {
case 1: printf("One");
break;
//....
case 20: printf("Twenty");
case 21:
case 22:
case 23: printf("Twenty-three");
}
If someValue is 1, "One" will be printed, since it break s out of that case.
If someValue is 23, "Twenty-three" will be printed, because there are no cases after it.
If someValue is 21 or 22, "Twenty-three" will still be printed, because they simply fall through to case 23's statments.
If someValue is 20, "TwentyTwenty-three" will be printed, because case 20 has its own statement
but still continues on to the statements in the next case (cases 21, 22, and 23).
That last sequence of events for case 20 is generally considered pretty evil, because it's hard to read and it's easy to make mistakes that way.
Lima doesn't have fallthrough, but there are a couple ways to write the same behavior in more readible ways.
This Lima code does the same thing as the C code above, use the statement:
cond fn[x: ret someValue==x]
1: wout['One']
;....
else: cond fn[x: ret x.has[someValue]]
{20 21 22 23}:
if someValue == 20: wout['Twenty'] ; only print 'Twenty' if the value is 20
wout['Twenty-three'] ; print 'Twenty-three' for all four values
-
Here are four very similar examples that show how using threads and the attributes
and affect program flow.
; the order of grabbing those files is potentially asynchronous (order doesn't matter),
; so they can all potentially be grabbed at once
; however output is assumed to be sequential (order matters) so this will always output
; the length of A.txt first, B.pdf second, and C.html third
; example output if
; A.txt is 100kb and takes 1 second to download,
; B.pdf is 10 bytes and takes 1 ms to download,
; C.html is 1000 bytes and 10 milliseconds to download, and
; we're ignoring the trivial amount of time the rest of the code takes:
;[
A.txt: 100000 - at 1 seconds
B.pdf: 10 - at 1 seconds
C.html: 1000 - at 1 seconds
;]
getLength = fn host file:
tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len
main:
real startTime = time[] ; time in seconds
var lengths = {}
df {"A.txt" "B.pdf" "C.html"} file:
lengths[file] = getLength['www.w3.org' file]
df lengths length file:
wout[file': 'length' - at: 'time[]-startTime' seconds'@]
; input is asynchronous here again, the default
; since output is also asynchronous between threads (obviously), its sequential per thread,
; the lengths will be printed as those files come in
; As far as speed, this program won't complete any faster than the previous example, but the first file printed may happen faster
; since A.txt might not be the first file to download, a file that took less time to download will be printed first
; example output with same conditions:
;[
B.pdf: 10 - at 0.001 seconds
C.html: 1000 - at 0.010 seconds
A.txt: 100000 - at 1 seconds
;]
getLength = fn host file:
tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len
main:
var lengths = {}
df {"A.txt" "B.pdf" "C.html"} file:
lengths[file] = getLength['www.w3.org' file]
df lengths length file:
thread: wout[file': 'length' - at: 'time[]-startTime' seconds'@]
; input now waits on the last input to complete, meaning that those files are pulled in synchronous order
; even tho output is running in separate threads, the output is forced to be sequential (A.txt first, B.pdf second, and C.html third)
; This way will definitely be slower since the files can't download in parallel
; example output with same conditions:
;[
A.txt: 100000 - at 1.011 seconds
B.pdf: 10 - at 1.011 seconds
C.html: 1000 - at 1.011 seconds
;]
getLength = fn host file:
tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len
main:
var lengths = {}
var nextFuture = future[nil] ; an immediately resolved future
df {"A.txt" "B.pdf" "C.html"} file:
nextFuture.wait:
nextFuture = future lengths[file] = getLength['www.w3.org' file]
df lengths length file:
thread: wout[file': 'length@]
; the input is now marked 'ready' and so is ready whenever the system wants to get it - at best (and in this case)
; that means compile-time
; the output is still asynchronous, but it wouldn't matter if it wasn't - the running time and output would be the same
; This way is the fastest since you don't have to do as much work at runtime (in this case its pretty much instant)
; The output may still be sequential (as shown), but that isn't required
; example output with same conditions:
;[
A.txt: 100000 - at 0 seconds
B.pdf: 10 - at 0 seconds
C.html: 1000 - at 0 seconds
;]
getLength = fn host file:
tcp.connect[adr[host 80] 'httpText["utf-8"]'].send['GET 'file' HTTP/1.0'@ @].len
main:
var lengths = {}
df {"A.txt" "B.pdf" "C.html"} file:
ready lengths[file] = getLength['www.w3.org' file]
df lengths length file:
thread: wout[file': 'length@]
-
In node.js, npm's package.json has devDependencies and optionalDependencies.
In lime (lima's package manger), devDependencies aren't needed because those can simply be put in the "ignore" list
so as to not be published at all.
They can be accessed via the package's development repository if wanted.
Optional dependencies aren't needed because Lima installs dependencies on run of a program that wants to load it,
rather than having a separate install command needed.
If a dependency fails to be loaded, the lima program trying to load it will see an exception and can deal with it appropriately if it wants to.
-
Program exit should always be under the full control of the top-level module.
Dependencies of a module shouldn't be allowed to hijack the program by killing it without going through
proper error handling channels first.
Therefore, the main program is allowed to change the exitFn attribute's bound value so
other code won't directly exit the program.
This can be used to capture the exit call and handle it appropriately. For example:
var exitHandler = fn code:
wout['Exit called within ShraffersExternalModule with code: 'code]
exitFn.as[exitHandler]:
ShraffersExternalModule.dangerousCall[]
-
So since Lima uses lists for a range of things (strings, arrays, streams, generatorss, etc), one question
would be whether back-pressure can be implemented in Lima. The answer is yes. All you have to do is have some external
condition for halting the output of a list until that external condition changes.
Here's an example:
; example forthcoming - Count the number of bytes that have been sent over the network for a file/stream
var input = tcp.connect[adr[<IP_ADDRESS>] 'utf8']
var output = tcp.connect[adr[<IP_ADDRESS>] 'utf8']
var paused=false middleMan = {}
df connection.in.split[''@] chunk:
if
chunk === 'backpressure':
paused = true
chunk === 'readyForMore':
paused = false
else:
middleMan.=cat[chunk]
df middleMan chunk:
if paused:
var f = future
change[paused]:
f.return[]
f.wait:
output.out.=cat[chunk]
-
Lima modules declared in the normal way are copies - modifying those copies won't affects anyone else
using that module.
For example, let's say you have the module:
; xModule
var x = 5
var getX = fn: ret x
If you use it like this in module A:
; module A
use['xModule']
xModule.x = 40
xModule.getX[] ; outputs 40
And then if module B uses xModule, it won't see those changes:
; module B
use['xModule']
xModule.getX[] ; outputs 5, not 40
This is different from things like node.js where, because variables are all references, changes would be seen.
Even if module A up there set a reference to xModule, it couldn't modify the contents module B would see:
; module A
private var xModule ~> load['xModule']
xModule.x = 40
xModule.getX[] ; outputs 40
; module B
private var xModule ~> load['xModule']
xModule.getX[] ; outputs 5, not 40
This is because load returns a copy of the module, not a reference to it. This is intentional - you don't want
your dependencies unexpectedly interfering with eachother.
If you want to see changes across modules, you can create a reference within xModule:
var x ~> 5
var getX = fn: ret x
This way, each copy of the module sees a reference to the same value.
While changing where the reference points to won't be seen by other modules,
overwriting the value the reference points to will be seen.
The safest way of doing this is to allow only one module (probably your entrypoint module) to
modify the dependency module (setting some global state).
This can be done by doing something like this:
; xModule
private var x ~> nil
var getX = fn:
if x == nil:
throw "X not yet set"
else:
ret x
var setX = fn val:
if x == nil:
x = val
else:
throw "X has already been set"
This way, you can only call setX by one module (whoever calls it first) and other attempts to mess with
global state will be met with an exception.
-
Languages like Java disallow certain types of equivalencies with generics.
For example,
List<Number> a = [];
List<Integer> b = a; // fails because a Number might not always be an Integer
In Lima, this isn't usually a problem.
An exception would be thrown only if an incorrect value actually gets set on the list:
list[real] w = {1 2 3}
list[int] x = w ; this is totally fine
list[real] y = {1.2 3.4}
list[int] z = y ; this throws an exception because y does not only contain ints
However, this can lead to some confusing cases, like if you want to write to a list of supertypes
when the object passed in is a list of subtypes:
var add4point4 = fn list[real] x:
x.=ins[4.4]
list[int] w = {1 2 3}
add4point4[w] ; throws an exception because 4.4 can't be added to w which is a list of ints
If you would like to add more type information about a variable that could be used to catch errors like this
at compile time, you could use the writeonly type to indicate contravariance.
-
Tho some of these things would be better to be done in more than one line,
this demonstrates how Lima uses some basic constructs to describle an infinity of possibilities.
Going through the equivalents for PHP's associative array functions:
; array_change_key_case(input, CASE_UPPER)
input.map[k.upper v]
; array_chunk(input, size)
input.group[k//size] ; keys preserve, so if you don't want that then do input.group[k//size].map[v.sort[k2:k2]]
; array_combine(keys, values)
keys[[ values[c] v]] ; values must be a list of elements
; array_count_values(input)
input.group[v].map[v[0] v.len]
; array_diff_assoc(array1, array2, ....)
array1[[ !({k:v} <<= array2 $ {k:v} <<= array3 $ ....) ]]
; array_diff_key(array1, array2, array3)
array1[[ ! ((array2.keys & array3.keys).has[k]) ]]
; array_diff_uassoc(array1, array2, keyCompareFunc)
array1[[ !(array2{[v2 k2: keyCompareFunc[k v k2 v2]]}.join[a$b]) ]]
; array_diff_ukey(array1, array2, keyCompareFunc)
array1[[ !(array2{[v2 k2: keyCompareFunc[k k2]]}.join[a$b]) ]]
; array_diff(array1, array2)
array1[[ !array2.has[v] ]]
; array_fill_keys(keys, value)
keys[[k: value k]]
; array_fill(5, 10, value)
(514){[v:value]}
; array_filter(input, callback)
input[[ callback[v] ]]
; array_flip(trans)
trans[[v k]]
; array_intersect_assoc(array1, array2, ....)
array1[[{k:v} << array2.cat[....]]]
; array_intersect_key(array1, array2, ....)
array1[[array2.cat[....].keys.has[k]]]
; array_intersect_uassoc(array1, array2, ...., compareFunc)
array1[[ array2.cat[....].map[] ]]
; array_intersect_ukey($array1, $array2, ...., $compareFunc)
array1[[ {k:v} << array2 $ {k:v} << array3 $ .... ]]
; array_intersect($array1, $array2, $array3, ....)
array1[[ (array2 & array3 & .....).has[v] ]]
; array_key_exists($key, $array)
array[key] != nil
; or
array.keys.has[key]
; array_keys($array, $value, true)
array[[v==value]].keys
; array_map($callback, $array)
array[[ callback[v] ]]
; array_merge_recursive($array1, $array2, ....)
; There is no simple equivalent to this difficult-to-understand php function - and there shouldn't be.
; array_merge($array1, $array2, ....)
; Like array_merge_recursive, this method doens't make any damn sense, but here it is:
fn[x arrs...:
var result = x
df arrs arr:
df arr v k:
if IsValidIndex[k]:
result.=ins[v]
else:
if result[k] == nil:
result[k] = v
else:
result[k] = {k: {result[k] v}
IsValidIndex = fn x:
ret x.in 0..00
][array1 array2 ....]
; array_multisort($array1, $array2) // heres another poorly designed php function
array1.sort[]
array2.sort[] ; why do you need to sort them in one function? Cause you're function names are so verbose?
; array_pad($input, $padSize, $padValue)
df 0..padSize v:
if input[v] == nil: input[v]=padValue ; that wasn't so hard was it?
; array_pop($array)
var result = array[array.len-1]
array.rm[array.len-1]
result
; array_product($array)
array.join[a*b]
; array_push($array, $val1, $val2, ....)
array.=cat[ {val1 val2 ....}]
;array_rand($input)
input[rand]
; array_rand($input, $num)
0..num[[ input[rand] ]]
; array_reduce($array, $function, $initial)
array.join[initial function[a b]]
; array_replace_recursive($array, $array1, ....)
fn[x arrs...:
var result = x ; create copy
df arrs arr:
df arr v k:
if IsList[result[k]] & IsList[v]: ; if both valures are lists
result[k] = fn.this[result[k] v]
else:
result[k] = v
var IsList = fn x:
ret x?.IterList != nil
][array array1 ....]
; array_replace($array, $array1, ....)
fn[x arrs...:
var result = x
df arrs arr:
df arr v k:
result[k] = v
][array array1 ....]
; array_reverse($array) ; don't preserve keys
array.keys.sort[-k] ; reverse keys
array.sort[k] ; then rekey
; array_reverse($array, true) ; preserve keys
array.sort[-k] ; assuming keys are currently in order
array.keys.sort[-k] ; no assumptions
; array_search($needle, $haystack, true)
haystack[[v == needle]].keys[0]
; array_shift($array)
var result = array[0]
array.rm[0]
result
; array_slice(array, offset, length, false) ; don't preserve keys
array[[(offset..(length-1)).has[k]]].sort[k]
; array_slice(array, offset, length, true) ; preserve keys
array[[(offset..(length-1)).has[k]]]
; array_splice($array, $offset, $length)
var condition = fn k: (offset..(length-1)).has[k]
var result = array[[condition]]
array.rm[[condition]]
; array_splice($array, $positiveOffset, $positiveLength, $replacements)
df array[[(positiveOffset..(positiveLength-1)).has[k]]] v k c: v = replacements[c]
; array_splice($array, $negativeOffset, $negativeEnd, $replacements)
df array[[((array.len-negativeOffset)..(array.len-negativeEnd)).has[k]]] v k c: v = replacements[c]
; array_sum
array[v+v2]
; array_udiff_assoc($array1, $array2, ...., $compare)
array1[[ !( array2[[k2 v2: k==k2 & compare[v v2] ]].join[a&b] $ ....) ]]
; array_udiff_uassoc($array1, $array2, ...., $dataCompare, $keyCompare)
array1[[ !( array2[[k2 v2: keyCompare[k k2] & dataCompare[v v2] ]].join[a&b] $ ....) ]]
; array_udiff($array1, $array2, ...., $compare)
array1[[ !(array2[[v2: compare[v v2] ]].join[a&b] & ....) ]]
; array_uintersect_assoc($array1, $array2, ...., $compare)
array1[[ array2[[v2 k2: k==k2 & compare[v v2] ]].join[a&b] & .... ]]
; array_uintersect_uassoc($array1, $array2, ...., $dataCompare, $keyCompare)
array1[[ array2[[v2 k2: dataCompare[k k2] & keyCompare[v v2] ]].join[a&b] & .... ]]
; array_intersect($array1, $array2, ...., $dataCompare)
array1[[ array2[[v2: dataCompare[v v2] ]].join[a&b] & ..... ]]
; array_unique(array)
array.sort[v][[!({v}<s)]]
; array_unshift(array, var1, var2, ....)
array.=ins[0 {var1 var2}]
; array_values(array)
array.map[v c]
; array_walk_recursive
var array_walk_recursive = fn inputs func userData:
var walk = fn value:
if value.len == nil:
func[v k userData]
else: df aList v k:
func[v k userData]
walk[inputs]
; array_walk(array, callback, other)
array.=map[callback[v k other] k]
; array(....)
{....}
; arsort(array)
array.keys.sort[-array[v]] ; integers
array.keys.sort[array[v]].sort[-k] ; anything
; asort(array)
array.keys.sort[array[v]]
; compact(varname, ...)
fn.this[[{varname ....}.has[k]]]
; count(array)
array.len
; extract(var_array)
private mix varArray
; in_array(needle, haystack)
{needle} < haystack
; krsort(array)
array.keys.sort[v].sort[-k]
; ksort(array)
array.keys.sort[v]
; list(a, b, c) = array('a','b','c')
{a b c} = {'a' 'b' 'c'}
; natcasesort(array)
array.map[v.lower].fsort[v.natcmp[v2]<0]
; natsort(array)
array.fsort[v.natcmp[v2]<0]
; range(low, high, step)
(low/step...high/step){[v*step]}
; prev, pos, current, next, reset
;[
function testArrayIteratorfunctions() {
$array = array('step one', 'step two', 'step three', 'step four');
// by default, the pointer is on the first element
echo current($array) . " \n"; // "step one"
next($array); // skip two elements
next($array);
echo current($array) . " \n"; // "step three"
prev($array); // rewind one
echo current($array) . " \n"; // "step three"
// reset pointer, start again on step one
reset($array);
echo current($array) . " \n"; // "step one"
}
;]
testArrayIteratorfunctions = fn:
var cur=0
var array = {'step one' 'step two' 'step three' 'step four'}
var i = array.iterList ; grab array's iterList, to iterate in the same order df would
; echo first element
wout[array[i[cur]] ' '@]
cur+=2 ; skip two elements
wout[array[i[cur]] ' '@]
cur-- ; rewind one
wout[array[i[cur]] ' '@]
cur = 0 ; reset index, start again on 'step one'
wout[array[i[cur]] ' '@]
; rsort(array)
array.sort[-v] ; numbers
array.fsort[!fn[ret v < v2][v v2]] ; strings
; shuffle(array)
array.sort[rand[]]
; sizeof(array)
array.len
; sort(array)
array.sort[v]
; uasort(array, cmp_function)
array.keys.fsort[cmpFunction[array[v] array[v2]]]
; uksort(array, cmp_function)
array.fsort[cmpFunction[k k2]]
; usort(array, cmp_function)
array.fsort[cmpFunction[v v2]]
-
; intersperse '.' "MONKEY"
; intersperse 0 [1,2,3,4,5,6]
"MONKEY".join[a.cat['.' b]]
{1 2 3 4 5 6}.join[{} a.cat[{0 b}]]
; intercalate " " ["heyyy","youuu","guyyyyyys"]
; intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]]
{"heyyy" "youuu" "guyyyyyys"}.join[a.cat[" " b]] ; notice that this is the same form as is used for string intersperse
{{1 2 3} {4 5 6} {7 8 9}}.join[{} a.cat[{0 0 0 b}]]
; transpose [[1,2,3],[4,5,6],[7,8,9]]
{0..00}.map[k: {{1 2 3} {4 5 6} {7 8 9}}.map[v[k]] ]
; fold (+) [1,2,3,4,5]
{1 2 3 4 5}.join[a+b]
; concat ["foo","bar","car"]
; concat [[3,4,5],[2,3,4],[2,1,1]]
{"foo" "bar" "car"}.join[a.cat[b]]
{{3 4 5}{2 3 4}{2 1 1}}.join[a.cat[b]
; concatMap function aList
aList.map[function[v]].join[a.cat[b]]
; and [True, False]
{true false}.join[a&b]
; or [True, False]
{true false}.join[a$b]
; any (==4) [2,3,5,6,1,4]
{2 3 5 6 1 4}.any[v==4]
; all (>4) [6,9,10]
{6 9 10}.all[v>4]
; iterate (*2) 2
{1..00}.map[v*2]
; splitAt 3 "heyman"
var x = "heyman"
{x[[k<3]] x[[k>3]]}
; takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]
; takeWhile (/=' ') "This is a sentence"
{6 5 4 3 2 1 2 3 4 5 4 3 2 1}.split[{3}][0]
"This is a sentence".split[" "][0]
; dropWhile (/=' ') "This is a sentence"
" ".cat["This is a sentence".split[" "][[k>0]].join[a.cat[" " b]]]
; span (/=' ') "This is a sentence"
var x = "This is a sentence"
{x.split[" "][0] " ".cat[x.split[" "][[k>0]].join[[a.cat[" " b]]]]}
; sort [8,5,3,2,1,6,4,2]
{8 5 3 2 1 6 4 2}.sort[v]
; group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
var newList = {}
df {1 1 1 1 2 2 2 2 3 3 2 2 2 5 6 7} x:
if x!=newList.sort[k][0][0]: newList.ins[{}]
newList[newList.len-1].ins[x]
newList
; map (\l@(x:xs) -> (x,length l)) . group . sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]
{1 1 1 1 2 2 2 2 3 3 2 2 2 5 6 7}.group[v].map[{v[0] v.len}]
; inits "w00t"
var result = {""}
df "w00t" x:
result.ins[result.sort[-k][0],x]
result
; tails "w00t"
var result = {"w00t"}
var last = fn: return result.sort[-k][0]
while last[].len > 0:
var next = last[]
next.rm[0]
result.ins[next]
result
; "cat" `isInfixOf` "im a cat burglar"
"im a cat burglar".find["cat"].len > 0
; "hey" `isPrefixOf` "hey there!"
"hey there!".find["hey"].any[v.keys.has[0]]
; "there!" `isSuffixOf`
var x = "oh hey there!"
x.find["there!"].any[v.keys.has[(x.len-1)]]
; partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"
var result = "BOBsidneyMORGANeddy".group[('A'..'Z').has[v]]
{result.true result.false}
; find (>4) [1,2,3,4,5,6]
{1 2 3 4 5 6}[[v>4]][0]
; 4 `elemIndex` [1,2,3,4,5,6]
{1 2 3 4 5 6}[[v==4]].k[0]
; ' ' `elemIndices` "Where are the spaces?"
"Where are the spaces?"[[v==' ']].keys
; findIndex (==4) [5,3,2,1,6,4]
{5 3 2 1 6 4}[[v==4]][0].keys[0]
; lines "first line\nsecond line\nthird line"
"first line"@,"second line"@,"third line".split[""@]
; unlines ["first line", "second line", "third line"]
{"first line" "second line" "third line"}.join[a,@b]
; words "hey these are the words in this sentence"
"hey these are the words in this sentence".split[" "]
; unwords ["hey","there","mate"]
{"first line" "second line" "third line"}.join[a," ",b]
; delete 'h' "hey there ghang!"
var x = "hey there ghang!"
x.rm[ x[[v=='h']].k[0] ]
; [1..10] \\ [2,5,9]
???
; [1..7] `union` [5..10]
1..7 $ 5..10
; [1..7] `intersect` [5..10]
1..7 & 5..10
; insert 4 [3,5,1,2,8,2]
var x = {3 5 1 2 8 2}
var y = 4
x.ins[x[[v>y]].k[0] y] ; why would you want to make this a core function in your language???
; Map.fromList [(1,2),(3,4),(3,2),(5,5)]
{{1 2}{3 4}{3 2}{5 5}}.map[{v[0]:v[1]}]
; Map.empty
{}
; Map.insert 3 100 Map.empty
{}[3][100]
; Map.null Map.empty
{} == {} ; duh
; Map.size Map.empty
{}.len
; Map.singleton 3 9
{3:9}
; lookup 3 Map.fromList [(1,2),(3,4),(3,2),(5,5)]
{{1 2}{3 4}{3 2}{5 5}}[3]
; Map.member 3 $ Map.fromList [(1,2),(3,4)]
{{1 2}{3 4}}.keys.has[3]
; Map.map (*100) $ Map.fromList [(1,1),(2,4),(3,9)]
{1:1 2:4 3:9}.map[v*100]
; Map.filter (>4) $ Map.fromList [(1,1),(2,4),(3,9)]
{1:1 2:4 3:9}[[v>4]]
; Map.toList . Map.insert 9 2 $ Map.singleton 4 3
{4:3}.map[{k v}]
; keys Map.fromList [(1,1),(2,4),(3,9)]
{1:1 2:4 3:9}.k
; elems Map.fromList [(1,1),(2,4),(3,9)]
{1:1 2:4 3:9}.v
;[
phoneBook =
[("betty","555-2938")
,("betty","342-2492")
,("bonnie","452-2928")
,("patsy","493-2928")
,("patsy","943-2929")
,("patsy","827-9162")
,("lucille","205-2928")
,("wendy","939-8282")
,("penny","853-2492")
,("penny","555-2111")
]
Map.lookup Map.fromListWith (\number1 number2 -> number1 ++ ", " ++ number2) phoneBook
;]
var phoneBook =
{{"betty" "555-2938"}
{"betty" "342-2492"}
{"bonnie" "452-2928"}
{"patsy" "493-2928"}
{"patsy" "943-2929"}
{"patsy" "827-9162"}
{"lucille" "205-2928"}
{"wendy" "939-8282"}
{"penny" "853-2492"}
{"penny" "555-2111"}
}
phoneBook.group[v[0]].map[ v.join["" a,", ",b[1]] ]
-
Tho some of these things would be better to be done in more than one line,
this demonstrates how Lima uses some basic constructs to describle an infinity of possibilities.
Going through the equivalents for Python's itertools library functions:
; count(10, 2)
(10..00)[[v%2==0]]
; cycle([1,2,3,4])
{1 2 3 4}*00
; repeat(10)
{10}*00
; chain([1,2,3], [4,5,6])
{1 2 3}.cat[{4 5 6}]
; compress([1,2,3,4], [1,0,1,0])
{1 2 3 4}[[{1 0 1 0}[k]]]
; dropwhile(lambda x: x<5, [1,4,6,4,1])
var x = {1 4 6 4 1}
var elements = x[[!(v<5)]] ; elements that don't match the criteria v<5
x[[ k>=elements.keys[0] ]]
; groupby(alist lambda x: x%4)
alist.group[v%4]
.map[v.sort[k]] ; to get rid of the key preservation that python doesn't have
; ifilter(lambda x: x%2, [10,11,12,13])
{10 11 12 13}[[v%2]]
; ifilterfalse(lambda x: x%2, [10,11,12,13])
{10 11 12 13}[[v%2 == 0]]
; islice([1,2,3,4], 2, 100, 4)
{1 2 3 4}[[ k in (2..100)[[k%2==0]] ]
; imap(func, list, list2)
list.map[func[v list2[k]]]
; starmap(func list)
list.map[func[v[0] v[1]]]
; tee(alist, 4)
(0..3).map[alist] ; makes a list of copies of alist
; takewhile(alist func)
df alist v k: if !func[v]:
alist.rm[[key: key>=k]]
; izip(alist, anotherList)
alist.map[if[anotherList[k] == nil: nil else: {v anotherList[k]}]
; izip_longest(alist, anotherList, fillvalue='-')
alist.map[if[anotherList[k] == nil: '-' else: {v anotherList[k]}]
-
A program that simple takes each line and creates a new file where each line is labeled with line numbers.
Python
out = open('uid list'+str(absoluteCount)+'.txt','w')
with open('mif_retarget_fbid_12_12_11.csv','r',encoding='utf-8') as f:
for line in f:
line = lines[n]
out.write("Line "+str(n+1)+"'"+line+"'\n")
if n%50000 == 0:
out.close()
out = open('uid list' + str(int(n/50000)+1) + '.txt','w')
out.close()
Lima
var input = file['mif_retarget_fbid_12_12_11.csv' 'utf-8']
df input.split[''@] line n:
file['uid list' n//50000+1 '.txt' 'utf-8'].=cat[ "Line " n+1 ": '"line"'"@]
Because Lima automatically handles file closure when there are no more references to that file, the programmer doesn't have to deal with that.
And because Lima will optimize access to the output file, a reference to the file doesn't need to be explicitly stored,
and can be conceptually opened on every iteration of the loop
(although any reasonable optimization will leave the file open over multiple iterations).
-
Taking an example, from lua's manual:
Lua
function downloadAndPrintLength (host, file)
local c = assert(socket.connect(host, 80))
local count = 0 -- counts number of bytes read
c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
while true do
local s, status = receive(c)
count = count + string.len(s)
if status == "closed" then break end
end
c:close()
print(file, count)
end
function receive (connection)
connection:timeout(0) -- do not block
local s, status = connection:receive(2^10)
if status == "timeout" then
coroutine.yield(connection)
end
return s, status
end
threads = {} -- list of all live threads
function get (host, file)
-- create coroutine
local co = coroutine.create(function ()
downloadAndPrintLength(host, file)
end)
-- insert it in the list
table.ins(threads, co)
end
function dispatcher ()
while true do
local n = table.getn(threads)
if n == 0 then break end -- no more threads to run
local connections = {}
for i=1,n do
local status, res = coroutine.resume(threads[i])
if not res then -- thread finished its task?
table.remove(threads, i)
break
else -- timeout
table.ins(connections, res)
end
end
if table.getn(connections) == n then
socket.select(connections)
end
end
end
host = "www.w3.org"
get(host, "/TR/html401/html40.txt")
get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")
get(host, "/TR/REC-html32.html")
get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")
dispatcher() -- main loop
Lima
downloadAndPrintLength = fn host file:
var c = tcp.connect[adr[host 80] 'httpText["utf-8"]']
c.send['GET 'file' HTTP/1.0'@ @] ; sending a request
wout[file c.len] ; prints the length of the data returned (waits until the connection is closed from the other end, since c.len isn't known until then)
get = fn host file: thread:
downloadAndPrintLength[host file]
main = fn:
host = 'www.w3.org'
get[host "/TR/html401/html40.txt"]
get[host "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf"]
get[host "/TR/REC-html32.html"]
get[host "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt"]
The vast difference in size of these implimentations comes from Lima's simple thread syntax and automatic optimization
(such that a thread may use coroutine-style under the hood),
Lima's ubiquitous list syntax that is used for any list-like entity,
and implicit futures (like c.len which blocks the execution of that thread until it is known).
-
fn functionName
int[a b]:
ret a+b
int a:
ret fn.this[a 1] ;[ here, if the function is called with only one argument,
then b defaults to 1 ;]
-
; right stripping spaces and extended spaces
var rstrip = fn aString:
var str = aString
df str v k:
if v == ' ' $ v.name == 'extendedSpace':
str.=rm[k]
else:
break
ret str
; left strip
var lstrip = fn aString:
ret rstrip[aString.sort[-k]] ; right strip the reversed string
This pattern can be used to strip any type of characters.
-
Encoding issues
Make a note that default parameter values and parameter types are evaluated on each function call,
not on function definition.
The meta properties need to have multiple scopes - one for each inherited object.
Question: should you be able to modify those scopes in lima? Probably not.
If not, mix must be a core macro because it needs to create those scopes.
Think about what `this` should do in an object definition space. Should it access the object containing the function its being run in? If so, maybe have self['prop'] to access own properties while you're building the object.
Leaning no: In python, any dedent must dedent to a previous indentation that has been written. Think about adding that constraint into Lima.
move fn.parse and var.parse into a separate object. Note that they'll need to have some additional parameters (at least the context):
parse.fn
parse.obj
probably move ALL IO to the standard library (and out of core), since pretty much all IO is machine/OS dependent
think more about the 'inputs', 'keyboard', and 'touch' objects and how they relate. keyboard and touch, at least, shouldn't be core probably.
Probably separate out most the encodings stuff into a separate module in the standard library
Consider a weak reference operator. Maybe ~>> or maybe ~|>
Maybe make it so . and : can't be combined with other operator symbols so you don't have issues doing things like a!.values and x...:
Maybe extend cast to somehow work on all types. Cast would constrain the type of an object to something, so it could be set to a variable with a more constrained type. (see cast in stanza)
Should types in a function's parameter list only be evaluated when the function is executed? If its done that way,
it would mean that you could use the interface of the object the method is contained in as a type inside the method
parameters.
If you make it so that functions evaluate the characters consumed for all macros in its body at function definition time,
then maybe introduce the concept of a macro consumption interface, which defines the number of characters
consumed by a macro. Using this, you could allow macros to be passed into or returned from functions and those macros
actually used inside the function as long as the consumption interface was identical. Example"
fn fn~! functionMacro:
var pseudoFunction = functionMacro a:
ret a
fn~! pseudoIf = getSomeMacro[]
pseudoIf pseudoFunction[5] == 5: // Assumes pseudoFunction has a bracket operator, although the macro
wout['ok!'] // interface doesn't require that.
How would you do @visibleForTest in Lima? Should probably be something where only the current module can access private
properties. Maybe have a proscribed 'tests' folder where anything in there can access private properties. Or more flexibly
maybe in the package.lima have a place where you can list test folder paths under which lima files have access to private
properties.
== Conventions ==
! as the interface operator
Ranges: inclusive or exclusive?
== concepts ==
Write a concept about dependency injection using attributes.
probably write a concept about list `len` and how to get a list's current length while its building using asynchronous
code (`thread`).
== How will types work? ==
Types matter in a couple key circumstances:
* When assigning a value to a variable
* When passing values into a function
* When returning values from a function
A type, at its core, is a function that checks if a value matches the conceptual type.
When a variable is set or when a variable is returned from a function, the value's type is checked.
When a variable is passed into a function as an argument, the function's dispatch list has type checking
functions that check the arguments to see which dispatch list matches.
Note that type macros have to have kinda weird functionality to properly support being used in parameters.
They have to respect an attribute that tells them to go into "parameter mode" and not evaluate default values,
which need to be deferred until its decided the parameter set is the matching one.
== variance ==
in one of the concepts there's an unfinished thought about variance: https://haxe.org/manual/type-system-variance.html .
It seems like in lima you shouldn't normally have to worry about covariance. But here's a case where you would:
list[int] a = {1 2 3}
ref[list[fra]] b ~> a ; totally fine, since a list[int] is also a list[number]
ref[list[complex]
b.=ins[4.4] ; fails because the value b points to (a) is a list[int] and can't contain non-integer values like 4.4
Here's another similar case:
list[int] a = {1 2 3}
f[a]
const f = fn list[fra] x:
x.=ins[4.4]
There are a couple problems with this:
1. The obvious - you can get sometimes a runtime error when inserting into something you thought was a list of that type (when its really a reference to a supertype)
2. Less obvious - you can't correctly type a reference to a supertype when you want to point it to a list of subtypes for writing only. Here's an example of that
list[fra] a = {1.1 2.2 3.3}
ref[list[int]] b ~> a ; errors because a contains values that aren't fra values
b.=ins[3] ; even tho all b wants to do is insert integers
There are a couple solutions to this:
A. Don't do anything - allow the runtime error and leave it at that
B. Require (or maybe just allow) a reference type to specify its variance https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
C. Disallow reference types entirely (actually this isn't a solution - nix it)
ref[const[list[fra]]] ; const only allows reading
ref[writeonly[list[int]]] ~> {1.1 2.2 3.3} ; writeonly maybe ignores types (so the type is just documentation?)
ref[const[list[fra]] x ~> {1 2 3} // is there a good way to make this succeed
ref[list[fra]] x ~> {1 2 3} // but this fail?
There is only if there is a defined subtype-supertype relationship.
Is it possible to always have a well defined relationship?
No its not, if you allow stuff like type.cond where there may not be any known way to determine
all the values that type.cond will return true for.
For types where a subtype-supertype relationship is unknown, the compiler probably should just emit a warning and
otherwise just treat it as a var type for the purposes of static type analysis.
Lima won't have the ability to always be 100% type safe.
In the variance debate, stanza takes the stand that covariance is the usual use case (reading) and that the type-checker won't yell about contravariance until a case of writing to an array of the wrong type actually occurs. Perhaps Lima should assume covariance unless the parameter is tagged with the writeonly type.
== var might need to be a superclass type of everything ==
In stanza, var is both a subclass of everything and a superclass of everything (maybe? they say so here:
http://lbstanza.org/optional_typing.html). In lima, it depends on whether or not we want this case to be caught by a
type-checker or not:
var x = fn writeonly list[var] a:
a.=ins['hi]
list[int] alist = {1 2 3}
x[alist] ; this will/can be caught by a static typechecker even if alist's elements can't be determined at compile-
time
It seems like maybe we would want var to be a subclass and superclass of everything (except nil).
There's actually a more important reason to do this. If you want to do optional typing that makes it easy to
incrementally add types, you need to be able to pass in a typed variable into a function that expects a var:
var f = fn var a:
doSomething[a]
int x
f[x] ; this shouldn't get stopped by a type checker
== implementation thoughts ==
All the operator overloading stuff is probably implementable via meta, and so are all types probably.
Throw and while can probably be implemented with jump
In javascript, exceptions will probably need to be creatively used to make jump work correctly in longjump situations.
Attributes are easy to implement until you need to consider asynchronous behavior. Without asynchronous behavior, an attribute could be a simple global variable that's set at the start, and its original value replaced at the end. However, in any place where an asynchronous continuation might start, a pointer to what the attribute currently points to must be made and that attribute reset in thread-local global scope at the start of that thread.
Operator definitions should throw an exception if the operator is already defined and `overload` isn't used.
=== make ===
How is something like `make` inherited or overridden? If you inherit from an object that has defined `make` and you want to override it, what do you do? If you don't mix in the bracket operator, the object returned by your new `make` won't have the originally defined bracket operator. Maybe store the original bracket operator in a property called `make` so an overriding `make` can grab it when it knows its overriding (which it could know if an `override` macro was used but not if the operator simply wasn't mixed in.
Maybe make needs to override the interface operator so that the interface will have the correct args for the bracket operator that make overrides.
== Logging/messaging ==
Probably have a standard logging module and written-down logging conventions so most people are doing it in a consisten way.
* Should provide similar information as an exception (module its from, file its from, line/column, etc)
* Caller should provide some kind of logging level (probably just an arbitrary string (that strongly recommends using the conventional debug/info/warn/error levels), an error code/type (another arbitrary string) that is intended to be static (maybe this would be better as an enum the module exports as part of its API), and additional error information (completely arbitrary).
* Code that calls functionality from its dependencies could then catch those messages and handle them appropriately, possibly doing things other than logging (for example cancelling that inner process and doing something else, which couldn't easily be done by simply throwing an exception (since the inner module could catch and ignore that))
== other ==
* move 'change' back to being a standard macro, and mention that it's implemented using macro[o].history
* Figure out how you might do something like observee.info(someValue).set(...) perhaps some kind of attribute?
* How would you do something like bluebird cancellation?
* I think I might want to add the ability to trigger a throw of an exception at whatever arbitrary part of a thread's continuation
* That way, you can throw a cancellation exception from any process (even tho this could be unsafe depending on the case)
and catch the exception somewhere in that same thread (or outside of the thread if it reaches the top of the stack).
* Atomic blocks would prevent an exception from being triggered while its statements are resolving
* Also, you could run a section of code with sideEffectLess (or isolate, or similar), making throwing an exception anywhere completely safe.
* Perhaps you can have a low level, unsafe mechanism for general throwing from a thread, and have a function that does this using isolate
which does it completely safely
* This reasoning basically was already included in the "justifications" file
* optimize needs to have a way to specify things like "optimize this for the 80% most likely inputs"
Last Updated: 2018-09-03
|