Source: rewrite/functional.js

  1. /**
  2. * Rewriting basic structures into functional structures
  3. */
  4. import curry from 'lodash/fp/curry'
  5. import flatten from 'lodash/fp/flatten'
  6. import * as Graph from '../graph'
  7. import * as Node from '../node'
  8. import {successors, predecessor} from '../graph/connections'
  9. import * as CmpRewrite from './compound'
  10. import {createLambda, createPartial, createFunctionCall} from '../functional/lambda'
  11. const letF = Graph.Let
  12. const distSeq = Graph.distributeSeq
  13. const sequential = Graph.sequential
  14. const createContext = (compound, parent, graph) => {
  15. return {
  16. inputs: Node.inputPorts(compound).map((input) => [input, predecessor(input, graph)]),
  17. outputs: Node.outputPorts(compound).map((output) => [output, successors(output, graph)]),
  18. parent
  19. }
  20. }
  21. function extendContext (context) {
  22. return (lambda, graph, ...cbs) => {
  23. const cb = Graph.flowCallback(cbs)
  24. return cb(Object.assign({lambda}, context), graph)
  25. }
  26. }
  27. function createLambdaNode (compound, parent, context) {
  28. return (graph, ...cbs) => {
  29. const cb = Graph.flowCallback(cbs)
  30. return sequential([Graph.addNodeIn(parent, createLambda(compound)), extendContext(context), cb])(graph)
  31. }
  32. }
  33. /**
  34. * @function
  35. * @name convertToLambda
  36. * @description
  37. * Create a lambda node that contains the given subset of nodes. It will not connect the inputs and
  38. * outputs use createCall for that.
  39. * @param {Location} parent The parent of the subset.
  40. * @param {Array<Location>} subset A subset of nodes in the graph that should be included in the lambda node.
  41. * @param {Portgraph} graph The graph
  42. * @param {Callback} [cb] A callback that is called after the lambda node is created. The context
  43. * will be an object that contains the lambda node, the predecessors of the subset and the successors of that subset. I.e.
  44. * the context object will look like this:
  45. *
  46. * ```
  47. * {
  48. * "lambda": "<The lambda node>",
  49. * "inputs": [[<inputPort>, <predecessor>], ...],
  50. * "outputs": [[<outputPort, [<successors>, ...]],...]
  51. * }
  52. * ```
  53. *
  54. * The graph is the new graph which includes the lambda nodes and in which the subset has been removed. The return
  55. * value of the callback function must be a graph (i.e. a graph in which you connect the remaining parts).
  56. * @returns {Portgraph} A new graph that replaced the subset with a lambda node. If a callback is given, the callback
  57. * is applied to the graph before `convertToLambda` returns and the return value of that callback is returned.
  58. */
  59. export const convertToLambda = curry((parent, subset, graph, ...cbs) => {
  60. const cb = Graph.flowCallback(cbs)
  61. return CmpRewrite.compoundify(parent, subset, graph, (compound, compGraph) => {
  62. const context = createContext(compound, parent, compGraph)
  63. return Graph.flow(
  64. Graph.removeNode(compound), // remove the old compound node in the end
  65. letF(createLambdaNode(compound, parent, context), cb) // create lambda node and pass information to callback
  66. )(compGraph)
  67. })
  68. })
  69. function createInputPartialsInternal (inputs, parent, from) {
  70. return (graph, ...cbs) => {
  71. const cb = Graph.flowCallback(cbs)
  72. if (inputs.length > 0) {
  73. return Graph.addNodeIn(parent, createPartial(), graph, (newPartial, graph) =>
  74. Graph.flow(
  75. Graph.addEdge({from: Node.port('fn', from), to: Node.port('inFn', newPartial)}),
  76. Graph.addEdge({from: inputs[0][1], to: Node.port('value', newPartial)}),
  77. letF(createInputPartialsInternal(inputs.slice(1), parent, newPartial), cb)
  78. )(graph))
  79. }
  80. return cb(from, graph)
  81. }
  82. }
  83. export const createInputPartials = curry((context, graph, ...cbs) => {
  84. return createInputPartialsInternal(context.inputs, context.parent, context.lambda)(graph, ...cbs)
  85. })
  86. const createCall = ([context, last], graph, ...cbs) => {
  87. const cb = Graph.flowCallback(cbs)
  88. return Graph.Let(Graph.addNodeIn(context.parent, createFunctionCall(context.outputs)), (call, graph) =>
  89. cb(call, Graph.flow(
  90. Graph.addEdge({from: Node.port('fn', last), to: Node.port('fn', call)}),
  91. flatten(context.outputs.map(([port, succ]) =>
  92. succ.map((s) => Graph.addEdge({from: Node.port(port, call), to: s}))))
  93. )(graph)))(graph)
  94. }
  95. /**
  96. * Takes a subset of nodes (all of them must have the same parent) and replaces them
  97. * by a lambda function, the partial application of their inputs and a call with all
  98. * outputs connected to the subsets successors.
  99. * @param {Array<Location>} subset A subset of locations identifying nodes which will be replaced by
  100. * a lambda call.
  101. * @param {Portgraph} graph The graph
  102. * @param {Callback} [cb] A callback that is called after the call is created with the newly created call.
  103. * @returns {Portgraph} A new graph in which the subset was replaced by a call to a lambda
  104. * function.
  105. */
  106. export const replaceByCall = curry((parent, subset, graph, ...cbs) =>
  107. replaceByThunk(parent, subset, graph, (payload, graph) => createCall(payload, graph, ...cbs)))
  108. const ternaryPack = (fn) =>
  109. curry((a, b, graph) => {
  110. return fn([a, b], graph)
  111. })
  112. /**
  113. * Takes a subset of nodes (all of them must have the same parent) and replaces them
  114. * by a lambda function, the partial application of their inputs It will not call the
  115. * lambda function and thus their outputs will not be connected. If you want to connect
  116. * the outputs after the call use replaceByCall. Information about the successors is accessible
  117. * via the context-callback.
  118. * @param {Location} parent The parent of the subset.
  119. * @param {Array<Location>} subset A subset of locations identifying nodes which will be replaced by
  120. * a lambda call.
  121. * @param {Portgraph} graph The graph
  122. * @param {Callback} [contextCallback] A context callback that is called after the thunk is created.
  123. * It has the signature [Context x Node] x Graph -> Graph . The context contains information about the
  124. * lambda node, and the successors. The second parameter is the last partial/lambda node that
  125. * outputs the thunk. The callback must return a graph which then will be the return value of this
  126. * function (replaceByThunk). The context object has the following structure:
  127. *
  128. * ```
  129. * {
  130. * "lambda": "<The lambda node>",
  131. * "inputs": [[<inputPort>, <predecessor>], ...],
  132. * "outputs": [[<outputPort, [<successors>, ...]],...]
  133. * }
  134. * ```
  135. *
  136. * The predecessors are already connected to create a thunk.
  137. * @returns {Portgraph} A new graph in which the subset was replaced by a call to a lambda
  138. * function. **Caution**: The new graph will not connect the output of the lambda function
  139. * use the context-callback to connect the outputs.
  140. * @example <caption>replaceByCall implementation</caption>
  141. * // create call is a context-callback that creates a call node and connects it properly
  142. * export const replaceByCall = curry((subset, graph) =>
  143. * replaceByThunk(subset, graph, createCall))
  144. * @example <caption>Log the not connected successors as an array.</caption>
  145. * replaceByThunk(subset, graph, curry((context, last, graph) => {
  146. * console.log(flatten(context.outputs.map((o) => o[1])))
  147. * }))
  148. */
  149. export const replaceByThunk = curry((parent, subset, graph, ...cbs) =>
  150. convertToLambda(parent, subset, graph, distSeq([createInputPartials, ternaryPack(Graph.flowCallback(cbs))])))