GithubHelp home page GithubHelp logo

q-fn's Introduction

q-fn

motivation

  • there exist repeated, higher-order idioms that are not trivially derived from stock adverbs
    • eg computing the arity of a function is surprisingly complicated in q to solve in a general sense
    • eg it is tricky to dynamically construct projections
    • etc
  • dynamic typing can be undesirable in cases whereby the grammar is well-defined but does not follow the desired semantics
    • eg want to declare that a function only accepts arguments of a certain type
    • eg want to be able to switch on type and apply matching functions
    • etc
  • the ability to treat a sequence of functions as a deque that behaves as a single composite on application is useful
  • cascading where semantics, akin to those in qsql, are useful generally
  • etc

interface

functions

namedescription
.fn.rnkdetermines the rank (arity) of a function given a function
.fn.tycomputes the recursive type given arbitrary k data
.fn.tddefines a typed function given a function and a type list
.fn.tsswitch an argument on type and apply the first match given a function list, type list list, and argument list
.fn.tswfswitch an argument on type and apply all matches given a function list, type list list, and argument list
.fn.dpchecks whether a name is defined given a symbol
.fn.projconstructs a projection given a function, indices, and arguments
.fn.projwrconstructs a projection given a function, its rank, indices, and arguments
.fn.dcmpdecomposes a function into its ordered constituents given a composite function
.fn.enqfenqueues a unary function to the front of a unary function given a destination and source
.fn.empfemplaces a unary function to the front of a unary function given a destination name and source
.fn.popfpops a unary function from the front of a composite unary function given a name
.fn.enqbenqueues a unary function to the back of a unary function given a destination and source
.fn.empbemplaces a unary function to the back of a unary function given a destination name and source
.fn.popbpops a unary function from the back of a composite unary function given a name
.fn.maproduces a cumulative mask whereby a set of given masks all hold true given a set of mask functions and target list
.fn.wafinds the indices where a set of masks all hold true against a list given a set of mask functions and target list
.fn.pmalike .fn.ma but allocates threads to each mask application
.fn.pwalike .fn.wa but allocates threads to each mask application
.fn.ma1like .fn.ma but uses a different algorithm that favors expensive or many masks
.fn.wa1like .fn.wa but uses a different algorithm that favors expensive or many masks
.fn.moproduces a cumulative mask whereby at least one of a set of given masks holds true given a set of mask functions and target list
.fn.wofinds the indices where a set of masks hold true for at least one mask given a set of mask functions and target list
.fn.pmolike .fn.mo but allocates threads to each mask application
.fn.pwolike .fn.wo but allocates threads to each mask application
.fn.mo1like .fn.mo but uses a different algorithm that favors expensive or many masks
.fn.wo1like .fn.wo but uses a different algorithm that favors expensive or many masks
.fn.ie(1) applies a function to the indices where a mask is true (2) applies a separate function to the indices where the same mask is false given a mask, target list, truthy function, and falsey function
.fn.ie1like .fn.ie but uses a different algorithm that favors expensive truthy and falsey functions

examples

q)/ .fn.rnk takes a [<function>]
q).fn.rnk{z}
3

q)/ it deals with composites and projections
q).fn.rnk['[upper;string]]
1
q).fn.rnk[upper string@]
1

q)/ it disambiguates between elisions and (::) literals
q).fn.rnk[{(y;z)}[;::]]
2
q).fn.rnk[{(y;z)}[;]]
3

q)/ it assigns the arity of enlist to 0N, by convention
q).fn.rnk[enlist]
0N
q)enlist[1;2;3]
1 2 3
q)enlist[1;2;3;4;5]
1 2 3 4 5
q)/ clearly, its arity is undefined

q)/ adverbs are of no concern
q).fn.rnk[{z}[;;::]/]
1
q).fn.rnk[+':]
2
q)/ .fn.ty takes a [<any>]
q)/ it returns its "recursive type" as used by other functions in this library
q).fn.ty[(4;1.;`a`b`c;(();()))]
-7h
-9h
11h
(();())
q)/ we recursively resolve all mixed lists to their typed constituents--except
q)/ (), which remains the same

q)/ .fn.td takes a [<function>;<ty tree>]
q)/ it mints a function that will fail with 'type if the argument fails to meet
q)/ the <ty tree>
q)f:.fn.td[+;-9 7h]
q)f[1.;til 10]
1 2 3 4 5 6 7 8 9 10f
q)f[1;til 10]
'type

q)/ if you're unsure what the <ty tree> is, just supply a sample argument list
q)/ to .fn.ty
q)g:.fn.td[{$[x>0;2*y;neg z]};.fn.ty(10;.z.p;-7)]
q)g[1;"p"$2021.01.01;2]
2042.01.02D00:00:00.000000000
q)/ unary functions minted with .fn.td, of course, may be enqueued or emplaced
q).fn.enqb[max;.fn.td[{where 0=mod[x;2]};99h]]17 8 9!0 1 2
17

q)/ .fn.ts takes a [<function list>;<ty tree list>;<argument list>]
q).fn.ts[({10};{11};{12});enlist each -7 -9 -7h;enlist 1]
10
q)/ notice how only the 1st function is applied

q)/ no matches is equivalent to a blank statement 
q).fn.ts[({10};{11};{12});enlist each -9 -9 -9h;enlist 1]
q)

q)/ .fn.tswf behaves like .fn.ts, but it applies all matched functions
q).fn.tswf[({10};{11};{12});enlist each -7 -9 -7h;enlist 1]
10 12

q)/ .fn.dp takes a [<symbol>]
q)/ it's a definition predicate--given some indirection
q).fn.dp`.fn.tswf
1b
q)y:10;delete x from`.;.fn.dp each`x`y
01b
q)/ .fn.proj takes a [<function>;<indices>;<arguments>]
q).fn.proj[{z};0 2;10 11]
{z}[10;;11]

q)/ .fn.projwr takes a [<function>;<rank>;<indices>;<arguments>]
q)/ specifying rank obviates the need for run-time reflection
q).fn.projwr[{z};3;0 2;10 11]
{z}[10;;11]

q)/ .fn.dcmp takes a [<composite>]
q)/ it returns its constituents in the order they execute
q).fn.dcmp('[{1};{2}])
{2}
{1}

q)/ it recursively decomposes as needed
q).fn.dcmp('['[{10};{z}11];{12}])
{12}
{z}[11]
{10}

q)/ .fn.enqf and .fn.enqb take a [<unary>;<unary>]
q)/ the "back" of a function executes first, ie .fn.enqb[f(g(x));h(x)] -> f(g(h(x)))
q)/ why not '[...]? compose does not check arity on its own. we want the deque
q)/ to execute wholly
q).fn.enqb/[(string;last;first)](1 2;3 4)
,"2"

q)/ .fn.empf and .fn.empb take a [<symbol>;<symbol>|<function>]
q)/ .fn.empf and .fn.empb behave like their enqueue analogs, but they mutate the
q)/ destination in-place
q)x:upper;.fn.empb[`x;string]
`x
q)x`hello
"HELLO"

q)/ .fn.popf and .fn.popb take a [<symbol>]
q)/ they pop the "front" and "back" functions, respectively, off a composite
q)f:.fn.enqb/[(string;last;first)];.fn.popf`f
$:
q)f
last*:
q).fn.popb`f
*:
q)f
last
q)/ .fn.ma takes a [<masks>;<list>], st <mask> := function that returns a boolean
q)/ vector of the same size as <list>
q)/ it returns a cumulative mask where all masks hold true
q).fn.ma[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
10100b
q)/ .fn.wa takes a [<masks>;<list>]
q)/ it applies where directly to the mask produced by .fn.ma
q).fn.wa[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
0 2

q)/ (.fn.pma, .fn.pwa) and (.fn.ma1, .fn.wa1) behave semantically as above
q)/ their use is described later
q).fn.pma[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
10100b
q).fn.pwa[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
0 2
q).fn.ma1[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
10100b
q).fn.wa1[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
0 2

q)/ .fn.mo takes the same arguments and has the same <mask> definition, but it
q)/ returns a cumulative mask where at least one mask holds true
q).fn.mo[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
10111b
q)/ .fn.wo takes a [<masks>;<list>]
q)/ it applies where directly to the mask produced by .fn.mo
q).fn.mo[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
0 2 3 4

q)/ likewise for (.fn.pmo, .fn.pwo) and (.fn.mo1, .fn.wo1)
q).fn.pmo[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
10111b
q).fn.pwo[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
0 2 3 4
q).fn.mo1[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
10111b
q).fn.wo1[(0=mod[;2]@;0=mod[;3]@;0=mod[;17]@);102 103 408 6 28]
0 2 3 4

q)/ .fn.wa and .fn.wb naively apply each mask serially and reduce the conforming
q)/ boolean vectors, .fn.pwa and .fn.pwb apply each mask in parallel and reduce,
q)/ and .fn.wa1 and .fn.wo1 apply the (i+j)th mask only where prudent. eg, if
q)/ mask i is false at index 2, and you're reducing with and, there's no need to
q)/ compute mask i+1, ... at index 2. this seems straightforward, but q's native
q)/ simd acceleration and cache treatment can allow primitives with o(n) behavior
q)/ to function as if they were constant time. the naive approaches in .fn.wa and
q)/ .fn.wb are therefore the fastest for relatively trivial masks, but you should
q)/ always measure
q)\s
16i
q)f:(0=mod[;2]@;0=mod[;3]@);x:1000000?10000000
q)\t:100 .fn.wa[f;x] / 1st
268
q)\t:100 .fn.pwa[f;x] / 3rd
396
q)\t:100 .fn.wa1[f;x] / 2nd
311
q)f:({","~'first each string 17.2<sqrt(x*x div 2)xexp 1.5};0=mod[;6]@)
q)\t:100 .fn.wa[f;x] / 2nd
5710
q)\t:100 .fn.pwa[f;x] / 3rd
8678
q)\t:100 .fn.wa1[f;x] / 1st
5594
q)f:.fn.proj[{0=x mod y};1]each til 100
q)\t:10 .fn.wa[f;x] / 3rd
1233
q)\t:10 .fn.pwa[f;x] / 2nd
863
q)\t:10 .fn.wa1[f;x] / 1st
148
q)/ .fn.ie takes a [<mask>;<list>;<truthy>;<falsey>]
q)/ it applies <truthy> at the indices where <mask> is true and applies
q)/ <falsey> where <mask> is false
q).fn.ie[.fn.ma .fn.proj[{0=x mod y};1]each 2 3;til 20;2*;neg]
0 -1 -2 -3 -4 -5 12 -7 -8 -9 -10 -11 24 -13 -14 -15 -16 -17 36 -19
q)/ integers that divide both 2 and 3 have been doubled, and the others had
q)/ their sign switched

q)/ .fn.ie1 is semantically identical, favoring expensive <truthy> or <falsey>
q)/ usually, the more vectorized your functions, the stronger .fn.ie will be
q)x:1000000?10000000
q)\t .fn.ie[.fn.ma .fn.proj[{0=x mod y};1]each 2 3;x;2*;neg]
34
q)\t .fn.ie1[.fn.ma .fn.proj[{0=x mod y};1]each 2 3;x;2*;neg]
94

pitfalls

q)/ the completeness of .fn.rnk comes at the cost of pricey scoping gymnastics
q)/ to my knowledge, there is no other way to generally compute arity by
q)/ reflection in q. ergo, try to move the cost from run-time to parse-time,
q)/ unless you're in the debugger
q)/ wrt .fn.proj, if you know the rank at parse-time, there is no reason to
q)/ use .fn.proj; use .fn.projwr
q)f:{z+3*y|x};g:.fn.projwr[f;3;2;100]
q)/ .fn.pt and .fn.rnkp are not configuration. do not change these
q).fn.pt:(::)
q).fn.proj[{};0 2;10 11]
'length

q-fn's People

Contributors

gitrj95 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.