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 languages 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:
logger.i['The time is: 't]
; the following statement throws an exception because t isn't declared until after the logger.i statement
var t = time.after:
logger.i['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 beginning of a year in TAI in ascending order.
logger.i[time.tai.years[0]] ; prints "0001-01-01 00:00:00"
df time.tai.years[[time[1999] < v < time[2020]]] y:
logger.i["It was a dark and storymy night, in the year "y.tai.y"...."@]
|
months |
A set of dates each representing the beginning of a month in TAI in ascending order.
|
weeks |
A set of dates each representing the beginning of a week in TAI in ascending order.
Days of the week start on monday.
logger.i["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
logger.i[v.tai.format[time.p["DW"]]@]
|
days |
A set of dates each representing the beginning of a day in TAI in ascending order.
|
hours |
A set of dates each representing the beginning of an hour in TAI in ascending order.
|
minutes |
A set of dates each representing the beginning of a minute in TAI in ascending order.
|
seconds |
A set of dates each representing the beginning 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 January 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.
mut 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]
mut 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 UTC 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 1095379200.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 1095379200
|
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
|
|
|
Object containing methods for dealing with files.
The behavior of this object can be overridden using the matr.fileObject module attribute.
Methods:
|
filePath |
Opens the file that has the given path.
Creates the file if it doesn't exist and creates any directories needed for the path.
filePath is a platform-independant path string in the style of unix filepaths.
The bracket-operator will concatenate any path parts passed.
It is opened for reading and/or writing (depending on how the program will use the file).
Since filePath is in unix-style, the folder separator is '/'.
The root folder is '/' and paths that don't begin with a '/' character are relative paths.
Also, '..' represents the previous directory.
For example, the windows path "C:\someFolder\someFile.txt"
would be represented as "/C/someFolder/someFile.txt" in Lima.
In a system without a root, like windows, '/' can be represented,
but can't have anything written to it in the normal way
(you'd have to mount a new drive to add something at the root).
Filepaths must be transcodable to the encoding dir.enc ,
and if not, an exception will be thrown.
Returns a file! object representing the bits inside the file
(bits representing the raw bytes, without OS specific conversions).
The file can be written to as if it was a and has all the methods and operators a normal bits-object has.
Can throw some errors, including: "unable to write", and "unable to overwrite", and "unable to open".
The returned object contains the following pseudo-members:
name |
Holds the file's name.
|
dir |
Holds a dir object representing the directory the file is in.
|
type |
Holds the type of file this object represents.
The values this can take on is filesystem dependent, but will generally be one of the following:
- file
- blockDevice
- characterDevice
- symLink
- fifo
- socket
|
close |
This pseudomember closes the file.
Attempting to read from or write to the file will reopen it.
|
rm |
This deletes the file.
|
flush |
This flushes the buffer for the file.
Should only be used if something absolutely needs that file to be updated immediately.
|
stats |
Contains filesystem specific information about the file.
An example of stats info for a file in a usual linux file-system:
{ dev= 2114,
ino= 48064969,
mode= 33188,
nlink= 1,
uid= 85,
gid= 100,
rdev= 0,
size= 527,
blksize= 4096,
blocks= 8,
atime= time[2011-08-10 23:24:11],
mtime= time[2011-08-10 23:24:11],
ctime= time[2011-08-10 23:24:11] }
|
meta |
This is a associative array that allows access to the file's metadata.
If the filesystem doesn't natively support arbitrary metadata,
lima will create a hidden file in the same directory named ".metadata"
and store the metadata there in LiON format encoded
in UTF-8.
The top level value will be an object that contains the metadata keyed with
the name of each file or directory within the directory the ".metadata" is in.
The value at each file/directory key will be an object containing the metadata.
|
filesystem |
Returns the filesystem in which the file resides.
Has the same members and methods that the filesystem member of a dir object has.
|
The returned object contains the following methods:
move |
stringPath |
Renames/moves the file to the new path and name.
|
unmake |
|
The file destructor flushes and closes the file.
|
dec |
encoding |
Returns a similar file object, except that it contains string characters instead of bits.
encoding should be the string-name of the encoding.
If encoding is not passed, this will use the string at metadata key
"fileEncoding" as the encoding, if available.
If not available for whatever reason, an exception will be thrown.
|
|
|
filePath encoding |
Returns a string-file obtained by decoding the file.
This is the same thing as:
; var x = file[path encoding]
var x = file[path].dec[encoding]
x.meta["fileEncoding"] = encoding
This construction ensures that the file will record its encoding for later use.
|
|
|
Returns a type that can only take on a file - an object returned by this object's bracket operator described above.
|
|
|
Object with methods for dealing with directories in the filesystem.
The behavior of this object can be overridden using the matr.dirObject module attribute.
Methods:
|
pathString |
Returns a directory literal.
If the directory does not yet exist, it does not create the directory,
and the object's methods treat it as if it was an existant, but empty, directory.
pathString contains the directory path.
pathString should have the same system-independant unix-style path
that file takes as a file path.
Directory paths must be transcodable to the encoding dir.enc ,
and if not, an exception will be thrown.
The returned object contains the following pseudo-members:
path |
Returns the system-independant path string of the directory.
Always contains a '/' at the end.
|
make |
Creates the directory and any directories it needs to create its path.
If a directory already exists, nothing will be done.
If something exists with that name that is not a directory, an exception will be thrown.
|
dirs |
A list of child directories keyed by their names.
|
files |
A list of files in the directory keyed by their names.
This list includes any file-system entity that isn't a directory.
|
up |
Returns the parent directory if the directory object isn't the root.
Returns nil if the directory object is the root.
|
rm |
Deletes the current directory and any containing files and folders.
|
str |
Its to-string method, prints the directory path.
|
filesystem |
Returns an object that has information about the filesystem and
functions for converting to and from filesystem-specific values.
It has the following members:
enc |
The encoding used by the filesystem (how it stores filenames, etc).
|
It has the following methods:
toNativePath |
limaPath |
Returns a native path for this filesystem from the given system-independent path.
|
fromNativePath |
fileSystemPath |
Returns a system-independent path given the given filesystem path.
|
|
The returned object contains the following methods:
move |
stringPath |
Renames/moves the directory and all of its contents to the new relative or absolute path.
Will throw an exception if something already exists at that path.
|
|
Members and unary operators:
|
Returns a type which constrains an object to only take on a directory.
|
cwd |
A object representing the current working directory of the program.
Modifying this modifies the cwd of the process.
|
main |
A object representing the directory the executable lives in.
|
module |
Returns a object representing the directory the module lives in.
|
|
adr |
Handles facilities for IP addresses and ports. Has the following methods.
|
addressFormat |
Macro operator that creates an address including IP-address, port, and scope.
Takes in a few different formats that it detects automatically:
- IPv4 address or IPv6 address.
- Address plus port.
- Address plus port.
- Address, port, and scope id (only for ipv6 addresses, defaults to global scope if not given)
- String of one of the previous three formats
- adr object plus port (errors if adr object already has a port)
- string ip address plus port (errors if adr object already has a port)
- adr object, port, and scope (errors if adr object already has a port or scope)
var address = adr[129.34.0.50]
var fullAddress = adr[53.0.0.40:80]
var ipv6Address = adr[2001:db8:85a3:8d3:1319:8a2e:370:7348]
var fullIpv6Address = adr[[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443]
var s1 = "129.34.0.50" ; ipv4 address
var s2 = "128.0.0.1:8080" ; ipv4 address plus port
var s3 = "2001:db8:85a3:8d3:1319:8a2e:370:7348" ; ipv6 address
var s4 = "[2001:db8:85a3:8d3:1319:8a2e:370:7348]" ; ipv6 address (with brackets)
var s5 = "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443" ; ipv6 address and port
var s6 = "[2001:db8:85a3:8d3:1319:8a2e:370:7348%3]:443" ; ipv6 address, scope, and port
var ipaddress = adr[s1]
var ipaddressWithPort = adr[s2]
var ipv6Address = adr[s3]
var ipv6AddressToo = adr[s4]
var ipv6AddressWithPort = adr[s5]
var ipv6AddressWithPort = adr[s6]
var addrAndPort = adr[ipaddress 5000]
var addrAndPort = adr["129.34.0.50" 5000]
var addrAndPortAndScope = adr[ipaddress 5001 3]
|
adr! |
|
The unary exclamation mark returns a type that can only take on an IP address object.
|
4 |
number port |
Returns an adr object containing the IPv4 address number represents.
var ipv4Addr = adr.4[500049000]
var ipv4Addr2 = adr.4[500049000 4000]
|
6 |
number port scope |
Returns an adr object containing the IPv6 address number represents.
var ipv6Addr = adr.6[3000298888837700 6000 3]
|
Also has the following pseudo-member:
own |
A list of adr! objects each containing the address for a network adapter.
|
interfaces |
An object where each key is the name of the network interface/adapter (human readable, not necessarily standard),
and each value is an adr! object representing the address for that named network adapter.
The values can be compared for equality to the elements in adr.own .
|
The object returned by the bracket-operator has the following members:
type |
Returns the name of the address family (usually 'ipv4' or 'ipv6').
|
str |
Returns the string form of the address (eg. "128.0.0.1:8080").
|
num |
Returns the address as an integer.
|
port |
The port number (nil if not set).
|
scope |
The scope value.
Throws an exception if not an ipv6 address.
|
|
udp |
Object for handling UDP packets.
The behavior of this object can be overridden using the matr.udpObject module attribute.
Has the following methods:
make |
destinationAdr deviceAdr |
Initializes and returns a UDP packet stream with the given destinationAdr (an adr! object).
The deviceAdr parameter is an adr representing the IP of the device to send and listen
from and is optional (defaulting to adr.own[0]).
Returns an extended chan object where appending to the out property
will send to the (destination) address and port given.
|
packet |
sourceAdr destAdr dataByteList |
Creates a UDP packet.
This isn't needed for normal UDP handling (as packets are constructed automatically),
but it can be useful if creating packets for direct socket use.
|
packet |
bitList |
Unserializes a bits bit-list object into a udp packet.
|
UDP sockets
UDP socket objects are returned by udp 's constructor and inherit from chan .
UDP sockets have the following additional method:
listen |
port |
Starts listening on the port passed in the sourceAdr parameter of the constructor,
giving the program an exclusive lock on that port.
Returns the calling object, for convenience.
|
UDP sockets also have the following additional members:
in |
Contains a list of all the UDP packets that will arrived on the udp socket from the
time listen was called to the time the udp port is closed.
|
out |
Appending a packet to this list sends that packet out on the socket.
Strings, bits , or UDP packets can be appended to out .
The UDP packet's source and destination will be used, if different from the UDP socket's.
|
source |
An object containing an adr! object representing the source address
- the address from which packets will be sent.
Holds nil if not listening.
|
dest |
An object containing a list of adr! objects representing the addresses to which messages will be sent
(regardless of which address messages are received from, if the object is listening on a port).
Holds an empty list if not initialized.
|
open |
Contains true if listening, false if closed.
|
close |
Pseudo-member that causes the object to stop listening to the port,
and returns the lock on that port.
Note that a properly running Lima runtime will properly close all connections even if the program itself
is forced to quit by an unexpected internal failure.
|
Note: Copies of udp, tcp, http, and raw socket objects that were listening/connected when copied
are also listening/connected.
UDP packets
The UDP packet objects returned by udp 's constructor and read from the out list of a UDP socket
have the following members and methods:
source |
An object containing an adr! object representing the source address.
|
dest |
An object containing an adr! object representing the address the packet was addressed to.
|
bits |
Contains the packet's bits serialization.
|
var udpSocket = udp[adr[34.8.0.1 8080]].listen[8080]
var message = udpSocket.in.=rm[0] ; gets the first packet
if message == 'hi':
udpSocket.out.ins['hi 'message.source] ; send message back
else:
udpSocket.out.=ins["Outta my way!"]
|
tcp |
Object for handling TCP connections.
The behavior of this object can be overridden using the matr.tcpObject module attribute.
Has the following methods:
listen |
port encodingName connections deviceAddress |
Listens for connections on the given code .
encodingName is required and defines how to decode incoming bytes and encode outgoing bytes.
connections is optional.
If connections isn't given, listen will
connect to the first incomming computer and return a single tcp stream object.
If connections is given, listen connects to that number of connections
and returns a list of those connections.
deviceAddress is the device to listen from and is optional (defaulting to adr.own[0]).
Lazily executes - doesn't block execution.
Execution won't be blocked until the program needs information it is waiting for.
Note that this means your program can use and process the first connection,
even if listen is still listening for more connections.
|
connect |
address encodingName sourcePort deviceAddress |
Attempts to connect to an address from the sourcePort .
encodingName is required and defines how to decode incoming bytes and encode outgoing bytes.
sourcePort is optional and if not given, an arbitrary free port
(ephemeral port) will be used - basically a port tcp.ports[arb[0..ports.len]] .
deviceAddress is the device to listen from and is optional (defaulting to adr.own[0]).
Returns an open connection.
|
packet |
source dest seq data ackNumber flags
windowSize urgentPointer options
|
Creates a TCP packet.
This isn't needed for normal TCP handling (as packets are constructed automatically),
but it can be useful if creating packets for direct socket use.
|
TCP sockets
The objects returned by tcp.listen and tcp.connect have the following members:
in |
A sequence (or bits object, depending on the encoding) of characters
that will arrive on the TCP socket from the time the connection was established to the
time it will close.
|
out |
Appending strings to this list sends the characters of that string out on the TCP socket.
Strings or bits can be appended to out .
|
open |
Contains true if the connection is open, false if closed.
|
close |
Pseudo-member that closes the connection.
Does nothing if connection is already closed.
|
source |
An object containing an adr! object representing the source address.
|
dest |
An object containing an adr! object representing
the destination address.
|
var connection = tcp.listen[8080] ; connects to another computer
var messages = connection.in.split[''@] ; messages delimited by newlines
if messages[0] == 'hi':
connection.out.=cat['hi'] ; send a message back
else:
logger.i[messages[0]] ; print message
|
inputs |
A list of objects representing the state of input devices like keyboards and controllers.
Each object in the list of inputs will have a number of keys, and each value at a key represents the
state of that key at a given time.
Many keys only have two states, and will then usually be represented as true or false.
Some other keys represent inputs with multiple states, like dials or scrollers,
and will usually have some number representing its state.
Note that the state of inputs are accessible regardless of whether the window is on top or in focus.
The behavior of this object can be overridden using the matr.inputsObject module attribute.
Inputs are intended to be used with event semantics:
change[inputs[0].a]:
if inputs[0].a:
; keydown
else:
; keyup
Like any input object, this can have more or less members depending on the device.
The members representing button states can be written to programatically for any device.
This value remains valid until the real device changes to a different state
(note that if the real device changes to the state the device was already changed to programatically,
it is as if the change never happened).
|
keyboard |
An object representing the state of the keyboard.
The members of the object are named after the keys they attach to.
if keyboard.f: logger.i['f is down']
if keyboard['=']: logger.i['equals is down'] ; non-letter keys are accessed as string keys
if keyboard.f3: logger.i['f3 is down']
if keyboard.shift && keyboard.3: logger.i['shift is down *and* 3 is down, is # down?']
keyboard['$'] ; always false for most keyboards, since there is no dedicated $ key (its a different state of the 4 key)
Keyboards also have a combo sub-object (which is an empty input object if a keyboard doesn't exist).
These differentiate between keyboard modes that change when certain keys
(like shift, fn, caps lock, ctrl, or alt) are activated or held down,
if the keyboard has specific meanings for combinations of state.
if keyboard.combo['#'] && ! keyboard.combo['3']: logger.i['# is down, but 3 is not']
keyboard.combo['#'] && ! keyboard['3']: ; for most keyboards, this is always false, because 3 and # are the same key
The keyboard object always has the following member:
active |
This is true if a keyboard exists, false if not.
This member is read-only.
|
This object always exists, even if there is no mouse or keyboard.
The touch and keyboard objects are also in the list of inputs , but this is shorthand.
Keyboard and touch objects are typed such that all members are allowed to be nil .
This way, if an input button doesn't exist,
any code can still access the button member and will not throw exceptions -
likely simply ignoring the related code since the state will never change.
This promotes cross-system compatibility and graceful failure.
|
touch |
A list object representing the state of the mouse, mice, and/or touch-events
- any device that has a position on the screen.
The list object itself has the following member:
max |
The maximum number of cursors it supports.
If this number is 0, it means there is no mouse/touch device.
|
The objects the list holds may have the following true/false members:
p |
A shape object containing the point at which the mouse/touch cursor is on the screen.
|
pri |
State of the primary mouse button.
Usually points to either L or R (usually L),
but may also be its own member in touch-screen cases (which don't have L or R).
|
sec |
State of the secondary mouse button.
Usually points to either L or R (usually R).
|
L |
State of the left mouse button.
|
R |
State of the right mouse button.
|
M |
State of the middle mouse button.
|
The objects the list holds can have the following integer member:
scroll |
State of the scroller.
|
when touch.any.p < p[{0 0}{0 10}{10 0}{10 10}]:
; do something when any mouse cursor hovers over the given square area
|
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 matr.dnsObject module 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 matr.socketObject module 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 matr.ipv4Object module 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 matr.ipv6Object module 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 matr.httpObject module 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] ; listens for connections
df listener.in request:
request.respond[503 nil "we're not on speaking terms"]
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 |
Pseudo-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 |
Pseudo-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 matr.httpsObject module 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}}
|
|
parse |
Object containing methods for parsing arbitrary strings as well as lima source code strings.
fn |
codeString paramList context |
Parses the codeString into a function immediate.
The context (optional) is an object containing
the following optional members:
- scope - An object where its members are variables in scope.
- this - An object that 'this' will reference.
- atr - An object where each property is a context attribute that will be available.
- matr - An object where each property is a module attribute that will be available.
The paramList (optional) is a list of parameter names the returned function will take.
mut i = 5
var theString =
'i++
ret x-1'
fn! f = parse.fn[theString {'x'} {scope:{i~>i}}]
f[8] ; i becomes 6 and returns 7
fn! f2 = parse.fn[theString {'x'} {}]
f[10] ; throws an exception because i is undeclared
var y=0
fn! f2 = parse.fn[theString {'x'} {scope:{i~>y}}]
f[99] ; y becomes 1 and returns 98
parse.fn and parse.var greatly increase safety of running
external code over the normal exec function in most programing languages
(which executes statements from the string inline). In Lima, these functions don't allow
any variables to implicitly leak into the evaluated code - the includes attributes.
|
parse |
codeString context |
Parses the codeString and executes the resulting lima code to build an object.
var o1 = parse.var['1 2 3']
o1[0] ; returns 1
var i = 5
var theString =
'a = 5
b = i
i = 9
c = fn: logger.i[this.i]'
var obj = parse.var[theString {scope:{i=i}}]
obj.a ; 5
obj.b ; 5
obj.i ; 9
obj.c[] ; prints 9
var x = 5
var codeString =
"p~>a
f = fn: p++"
var obj2 = parse.var[codeString {scope:{a~>x}}]
obj2.f[] ; x becomes 6
|
|
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 load['someModule@1.0.1/some/internal/module'] .
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 = load['markdown@2.0.0/dist/helper']
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
- errorChecking - Set to "strong" if you want the compiler to fail when it finds at least
one `StrongError` that could potentially be thrown.
If `errorChecking` is set to "weak" or omitted, the compiler will
only fail when it can determine that at least one exception
(`StrongError` or not) will *always* happen.
In the future, this might have a form that allows per-file errorChecking
configuration.
- 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).
- ignore - files and folder to ignore when publishing the package
- preventPublishing - If true, publishing the package will be prevented whenever possible.
- 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).
|
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: logger.i["The first"]
personB: logger.i["The second"]
else: logger.i["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.
|
|
|
:
[
]
|
Just like a rawthread , but its statements are 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 truly 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.
This is more similar in concept to setTimeout in a language like Javascript than threads in
a language like C or Java.
|
:
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 this.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 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 object's destructor is explicitly overwritten with a different destructor or deleted,
for example, if an object in a nilable variable is set to nil.
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 (undestructed)
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 reverse-order the objects were created.
To control the order of destruction, a nilable 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 .
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 becoming 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.
|
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:
use['x@2.1.0' '../y/someModule']
var[ x=load['x@2.1.0'] 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
logger.i[x] ; works fine
logger.i[z] ; doesn't work
return x+y+c
logger.i[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".
|
change[testStatement:oldValue changeStatments]:
statements
|
On-change event statement.
The statements are run when one of the given testStatements changes value.
For each testStatement, oldValue (and the preceding colon) is optional,
but if declared, the name given for oldValue can be
used to access the previous value of the testStatement.
Returns an object with the following pseudo-methods
stop - stops the listener (the event-handler won't
be run for subsequent changes to the testStatement).
The event-handler is triggered synchronously on change of whatever variable caused the testStatement
to change.
After any triggered event-handlers run, the code that triggered the change is continued from.
Any event-handler triggered simultaneously in a change event are executed in parallel atomic blocks.
mut x = 5
change[x]:
logger.i['x changed to 'x]
x = 6
; the event handler prints to the console before the following statement
logger.i['hi']
; keep track of the change in value
mut delta = 0
change[x<5:lastValue]:
delta = lastValue - x
; maintain a = 2*b+c^2
mut a = 2*b+c^2
change[c b]:
a = 2*b+c^2
Note that you have full control over the number of times the statements are executed
because one event is handled fully before the next event can be handled.
Even though an event is fully handled before the next one is handled,
all events are guaranteed to be caught, even if one happens while another one is being handled.
This is implemented using the history meta property.
|
Standard Stack Attributes
logger[level message data] |
Holds a function that handles logs. Has the same form as logger[] .
By default, prints all levels except 'warn', 'error', and 'severe' to stdout. The 'warn', 'error', and 'severe' levels are printed to stderr by default.
|
Lima's philosophy is pretty similar to "the-right-thing" approach, with reversed importance between consistency and simplicity, and some additional caveats about completeness. This is very much not a "worse-is-better" philosophy. A general purpose programming language is basically the base layer of all software, and it should be done right, not quick and dirty. In comparison to the "get-stuff-done" approach, Lima's philosophy is similar except that performance isn't really a factor, and Lima's philosophy is more specific about what it means by completeness.
- Correctness - Incorrectness is not allowed. The implementation must match the spec. It’s pointless to try to get stuff done if you can’t guarantee the result is correct.
- Completeness - Completeness is important and the design must allow for eventual completeness. This comes with two caveats:
- The completeness here should mean "the ability for the programmer to create all useful interfaces and abstractions", *not* that "the language should implement all useful interfaces and abstractions". So a complete language is (defined here as) one where any incompleteness can be implemented in userspace. Some way is better than no way.
- Completeness now can be sacrificed to get things done. Some abilities are harder to implement and have fewer use cases. Those can wait for lower hanging fruit. It's better to get some stuff done now than wait until everything can get done later, but completeness should be left open as a possibility for later.
- Simplicity - Simplicity is more important than everything except correctness and eventual completeness, Its more important for the interface to be simple than the implementation. The faster the programmer can get stuff done, the better. It’s more important to make things easier on the programmer than it is to make things easier on the language implementer.
- Consistency - Consistency can be sacrificed for simplicity. Don’t let excessive consistency get in the way of getting stuff done.
- Performance - Runtime speed is neither sacrificed nor prioritized, because lima is runtime independent. If anything, performance is last priority, because performance isn't the responsibility of the language. Instead, the responsibility of the language is to allow the programmer to express the constraints that optimizers can then use to make the program run fast. Simplicity, consistency, and completeness are never compromised for performance. However, having language constructs that allow for expressing information that is required for optimizers to do their job is a priority.
Lima's guiding principles:
- Simple grammar. The language should be easy to parse for both humans and computers.
- No behavioral distinction between runtime and compile-time. Any distinction is an optimization that should be done out-of-band from the behavioral description.
- Full type safety as an option, not a requirement.
- Fully memory safe. There is no "this random number is really a pointer, trust me."
- No crashes. A program that compiles should never crash (although it may hang or do something surprising but documented).
- Helpful error messages. Errors should be both programatically handleable and human readable. Errors should contain messages that are understandable and actionable. Avoid jargon related to the implementation that is unrelated to the API.
- Inherent build system. No separate applications are required to configure or build Lima programs.
- Aim to reduce common programming bugs through the use of powerful and DRY abstractions, rather than restrictive syntax. Restrictions (like typing) should be opt-in.
- Provide a single, clean and clear way to do things rather than catering to every programmer’s preferred prejudices.
- Make upgrades clean. Do not try to merge new features with the ones they are replacing, if something is broken remove it and replace it in one go. Where possible provide rewrite utilities to upgrade source between language versions.
- Reasonable build time. Compilation should not block the development process.
- Fully defined semantics. The semantics of all language features must be available in the standard language docs. It is not acceptable to leave behavior undefined or "implementation dependent".
- Document required complexity. Not all language features have to be trivial to understand, but complex features must have full explanations in the docs to be allowed in the language.
- Language features should be minimally intrusive when not used.
- Interoperability. Must be interoperable with other languages in some way.
- Avoid library pain. Use of 3rd party libraries should be as easy as possible, with no surprises. This includes writing, distributing, and using multiple versions of a library in a single program.
-
In some languages, you can access 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
logger.i "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[]
logger.i[class.myStaticProperty] ; prints 1
logger.i[newInstance1.myStaticProperty] ; prints 1
var newInstance2 = class[]
logger.i[class.myStaticProperty] ; prints 2
logger.i[newInstance1.myStaticProperty] ; prints 2
logger.i[newInstance2.myStaticProperty] ; prints 2
class.myStaticProperty = 3
logger.i[class.myStaticProperty] ; prints 3
logger.i[newInstance1.myStaticProperty] ; prints 3
logger.i[newInstance2.myStaticProperty] ; prints 3
newInstance1.myStaticProperty = 4
logger.i[class.myStaticProperty] ; prints 4
logger.i[newInstance1.myStaticProperty] ; prints 4
logger.i[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[]
logger.i[class.myStaticProperty] ; prints 16
logger.i[newInstance1.myStaticProperty] ; prints 16
logger.i[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 pseudo-method
ins and the join method:
var L2 = {1 2 3 4 5}.ins[[', ']] ; this returns the list {1 ', ' 2 ', ' 3 ', ' 4 ', ' 5}
logger.i[L2.join[a.cat[b]]] ; this will print the list with commas
-
The trick is to make a reference to the object itself
x =
{ mythis ~> this ; mythis points to this
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
logger.i a ; errors, because a is an ambiguous name
logger.i this.a ; prints "hi"
logger.i this.b ; prints "hi" again
logger.i mythis.a ; prints 5
logger.i c ; prints 89
logger.i mythis.c ; also prints 89
]
}
}
-
-
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:
logger.i[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:
logger.i 'cry'@
]
or a generator:
createInfiniteList = fn:
var theList = {}
var next = 1;
while true:
infiniteList.=ins[next]
next++
ret theList
infiniteList = infiniteList[]
logger.i[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 :
logger.i[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[node!]? next
}
reverse = fn mut node! head:
mut ref? previous ~> nil
while head != nil:
{head.next previous head} ~> {previous head head.next}
head = previous
main:
node! 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 this.f ; int accessor
]
acc s
[ ret this.f ; string accessor
]
}
-
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
-
mut 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[ mut[a=2] b=5 c=13]
fn! aWire = fn[ ret c+b*a ] ; acts like a wire
logger.i aWire[] ; will print '23' (13+5*2)
a+=4
logger.i 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 languages, 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:
mut 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'
logger.i[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:
mut 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
logger.i[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:
mut x = 5
mut a = {1 2 3}
var slice = a.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
-
mut 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}
-
If you want an enum in Lima, you can create type sets. For example:
type color = type.set['blue' 'green' 'purple']
fn[[color:]] colors = fn int x:
if x == ?0::
1: ret 'blue'
2: ret 'green'
3: ret 'purpel' ; Compiler should complain that the misspelling of 'purple' doesn't match the function's return type.
var c = colors[1];
If you don't need/want to explicitly enumerate all possible values, you can return strings and in most cases
your IDE or compiler should still be able to tell you about code paths that can never execute.
var colors = fn int x:
if x == ?0::
1: ret 'blue'
2: ret 'green'
3: ret 'purple'
var color = colors[1];
if
color == 'green':
logger.i['GREEN!']
color == 'purpel':
logger.i['PURPLE!'] ; IDE/compiler/interpreter should tell you this code path can never happen because the function will never return that misspelling 'purpel'
-
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]:
logger.i['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:
logger.i['y was set to a different object']
df d.elements v:
if v.type == 'add':
logger.i['y got a new element: 'v.val]
v.type === 'move':
logger.i['y got shuffled around']
v.type === 'remove':
logger.i['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 mut int a = 5
int *p, *p2 mut ref.int[p p2]
int** p3, **p4 mut 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: logger.i['One']
;....
else: cond fn[x: ret x.has[someValue]]
{20 21 22 23}:
if someValue == 20: logger.i['Twenty'] ; only print 'Twenty' if the value is 20
logger.i['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:
var startTime = time.before:
mut lengths = {}
df {"A.txt" "B.pdf" "C.html"} file:
lengths[file] = getLength['www.w3.org' file]
df lengths length file:
var endTime = time.after:
logger.i[file': 'length' - at: 'endTime-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:
mut lengths = {}
df {"A.txt" "B.pdf" "C.html"} file:
lengths[file] = getLength['www.w3.org' file]
df lengths length file:
var endTime = time.after:
thread: logger.i[file': 'length' - at: 'endTime-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:
mut lengths = {}
mut 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: logger.i[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
mut lengths = {}
df {"A.txt" "B.pdf" "C.html"} file:
ready lengths[file] = getLength['www.w3.org' file]
df lengths length file:
thread: logger.i[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:
logger.i['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[128.123.43.0] 'utf8']
var output = tcp.connect[adr[128.123.43.1] 'utf8']
mut 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 mut 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 describable 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...:
mut 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...:
mut 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...:
mut 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:
cur 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
logger.i[array[i[cur]] ' '@]
cur+=2 ; skip two elements
logger.i[array[i[cur]] ' '@]
cur-- ; rewind one
logger.i[array[i[cur]] ' '@]
cur = 0 ; reset index, start again on 'step one'
logger.i[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]
mut 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"
mut result = {""}
df "w00t" x:
result.=ins[result.sort[-k][0],x]
result
; tails "w00t"
mut result = {"w00t"}
mut last = fn: return result.sort[-k][0]
while last[].len > 0:
mut 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 describable 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
logger.i[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 implementations 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:
mut 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
Think about control characters in strings. What if someone copies ascii into your program - how are you supposed to deal with that? Maybe omit escape characters (with some other way of adding them in), but allow them in strings?
Add to the safe traversal operator that if a bracket access throws an exception, it'll return nil
For make, rather than getting rid of it and using functions instead, change `make` into something that can be done in user-space by allowing creation of custom meta properties using unique ids. So if a module that defines something like make wanted to add a property to an object's meta data, you would create an ID object (that has a hashcode dependent on its identity and compares against its identity) and use that as the key for the meta data. For example:
makeMetaInfoKey = IdObject ; If another module wants to use the data stored in this key,
; they'd need a reference to this object
makeFn = fn constructor:
var mobj = meta[callingScope['this']]
if !mobj.info.has[makeMetaInfoKey]:
mobj.info[makeMetaInfoKey] = {var[originalBracketOp originalExclamationOp]}
else:
; In this case, maybe have some easier way to access the super constructor from the new constructor
var info = mobj.info[makeMetaInfoKey]
info.originalBracketOp = mobj.operators['[']
info.originalExclamationOp = mobj.postOperators['!']
mobj.operators['['] = constructor
mobj.postOperators['!'] = fn:
return {
mix[mobj![
operator[ [ ] = info.originalBracketOp
postOperator[ ! ] = info.originalExclamationOp
]
}
meta[callingScope.this].info[makeMetaInfoKey]
callingScope.this
callingScope
Maybe a?[b] should be able to be use to set values too. Eg
var a = {}
a?[b] = 5 ; a now contains {b::5}
a?[b]?[c] = 9 ; a now contains {b::{c::9}}
https://blog.kentcdodds.com/write-your-own-code-transform-for-fun-and-profit-140abde9c5c6
Babel plugins have great ideas about code transformation
https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-asts
The interface operator should probably represent an interface of whatever the object holds at the time the type is checked, rather than sealing the type based on what the object held when the operator was called. One major reason for this is recursive types, or object where its properites might contain a value with the interface of the object itself (eg `this!`).
optimizers should be created from code transformers. A code transformer knows how to do a particular transformation, but has no logic about when that transformation is appropriate. An optimizer should use an analyzer to collect the information it needs, the optimizer should then decide which transformations to do, and then do them.
Maybe optimizers should be written to take in one new piece of information at a time, so that they're written to run well on incremental changes (vs from-scratch rebuilds).
Would it be possible to write a transformer to transform from and to a high level language, and then convert the transformer to transform to and from a different (lower level) langauge? If so, it could make optimizers much easier to write.
Use :> to indicated default arguments so destructuring assignments and parameter lists can look the same
attribute declarations (with types - must be public and at the top-level in a module)
Consider adding an operator that turns normal functions into "pipeline" form. For example:
a[] -> b[x] -> c[] -> d[1 2 3]
would be the same as:
d[c[b[a[] x]] 1 2 3]
So A -> B[...] would be transformed into B[A ...]
Alternatively, it A -> B ... could be transformed into B[A ...], which would make the above example:
a -> b x -> c -> d 1 2 3
How bout | (and use || for nil coalescence)
a | b | x | c | d 1 2 3
Piping into a different parameter than the first:
a |2 b 1 |3 c 1 2 4 5 |'paramName' fnWithOptionals
== Documentation structure ==
Rethink the intro using http://cone.jondgoodwin.com/coneref/showcase.html
think more about the 'inputs', 'keyboard', and 'touch' objects and how they relate.
Maybe separate out most the encodings stuff into a separate module in the standard library
Add an example (to the intro) of defining and using a DSL with `macro`. Eg a parsing DSL that defines how to parse and unparse
a character encoding or file format.
Make it more clear which parts of the documentation are an exhaustive reference and which are guides.
== Optimization ==
Add to the an example of `optimize` and other features like that (input expectations, performance tradeoffs)
* optimize needs to have a way to specify things like "optimize this for the 80% most likely inputs"
== 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`).
Write a more involved concept on optimize[] that explains what it does and examples of when it would be useful. Try to do an
end to end example with optimizer-module code.
Write a concept on how to do something like observee.info(someValue).set(...) using an attribute.
== Types ==
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)
Think about how you might implement implicit generics. Literals are a problem here, but you could do inferred typing.
typeParam T
addToList = fn list[T] theList T theItem: ; The idea is T must match, but how is that matching done?
theList.ins[theItem]
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.
Maybe have a way to set a typing system for a particular file/module/project. This would be configured in package.lima and
constrain the relevant source files to using only types that are subsets of types in a configured set of types.
nim's distinct types seem pretty useful for preventing spaceships from crashing
Pony and rust both have ways to specify ownership of a reference to a value and mutability plays a role in what's allowed.
Think about how you can specify that kind of thing in Lima.
Think about having the possibility of type data, where types can pass around data in certain circumstances.
This could be used to replicate the ownership models of pony and rust.
=== 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
=== 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.
== other ==
How does atomic work when it needs input or output? You might need a way of describing when an object used in two different atomic statements doesn't conflict, and when two objects used by two different atomic statements *do* actually conflict for optimization purposes in the first case, and safety purposes in the second case. For example, if two atomic statements are talking to the same server.
Think about how to deal with arb in dev vs prod, and how to make sure you can repro bugs from someone else's machine
in the face of behavior that switches based on hardware performance metrics (that might even be taken real-time at runtime).
Maybe have some record of what arb choices are made and be able to use that record to force arb choices in another build.
Module loading shouldn't cause circular dependencies unless there's an actual circular value dependency. This might require the use of implicit futures or something. But for example, this should be totally fine:
Module A: Module B:
use['./b'] use['./a']
a1 = 5 b1 = 1
a2 = fn: b2 = fn:
ret b1 ret a1
While this should actually throw an exception:
Module A: Module B:
use['./b'] use['./a']
a1 = b1 b1 = a1
Revisit "Why Lima doesn't need devDependencies and optionalDependencies"
Last Updated: 2018-09-03
|