Merge pull request #591 from quantified-uncertainty/declare-test
Declare() functionality
This commit is contained in:
commit
70664c0a91
|
@ -11,12 +11,36 @@ import {
|
|||
defaultImports,
|
||||
defaultBindings,
|
||||
defaultEnvironment,
|
||||
declarationArg,
|
||||
declaration,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { NumberShower } from "./NumberShower";
|
||||
import { DistributionChart } from "./DistributionChart";
|
||||
import { ErrorBox } from "./ErrorBox";
|
||||
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
|
||||
|
||||
function getRange<a>(x: declaration<a>) {
|
||||
let first = x.args[0];
|
||||
switch (first.tag) {
|
||||
case "Float": {
|
||||
return { floats: { min: first.value.min, max: first.value.max } };
|
||||
}
|
||||
case "Date": {
|
||||
return { time: { min: first.value.min, max: first.value.max } };
|
||||
}
|
||||
}
|
||||
}
|
||||
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
||||
let range = getRange(x);
|
||||
let min = range.floats ? range.floats.min : 0;
|
||||
let max = range.floats ? range.floats.max : 10;
|
||||
return {
|
||||
start: min,
|
||||
stop: max,
|
||||
count: 20,
|
||||
};
|
||||
}
|
||||
|
||||
const variableBox = {
|
||||
Component: styled.div`
|
||||
background: white;
|
||||
|
@ -216,6 +240,24 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
|||
/>
|
||||
</VariableBox>
|
||||
);
|
||||
case "lambdaDeclaration": {
|
||||
return (
|
||||
<VariableBox heading="Function Declaration" showTypes={showTypes}>
|
||||
<FunctionChart
|
||||
fn={expression.value.fn}
|
||||
chartSettings={getChartSettings(expression.value)}
|
||||
height={height}
|
||||
environment={{
|
||||
sampleCount: environment.sampleCount / 10,
|
||||
xyPointLength: environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return <>Should be unreachable</>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"tickOpacity": 0.0,
|
||||
"domainColor": "#fff",
|
||||
"domainOpacity": 0.0,
|
||||
"format": "~g",
|
||||
"format": ".9~s",
|
||||
"tickCount": 10
|
||||
}
|
||||
],
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"name": "x",
|
||||
"type": "linear",
|
||||
"nice": true,
|
||||
"zero": false,
|
||||
"domain": {
|
||||
"data": "facet",
|
||||
"field": "x"
|
||||
|
@ -31,7 +32,7 @@
|
|||
"type": "linear",
|
||||
"range": "height",
|
||||
"nice": true,
|
||||
"zero": true,
|
||||
"zero": false,
|
||||
"domain": {
|
||||
"data": "facet",
|
||||
"field": "y"
|
||||
|
@ -58,6 +59,7 @@
|
|||
"tickOpacity": 0.0,
|
||||
"domainColor": "#727d93",
|
||||
"domainOpacity": 0.1,
|
||||
"format": ".9~s",
|
||||
"tickCount": 5
|
||||
},
|
||||
{
|
||||
|
@ -69,6 +71,7 @@
|
|||
"tickOpacity": 0.0,
|
||||
"domainColor": "#727d93",
|
||||
"domainOpacity": 0.1,
|
||||
"format": ".9~s",
|
||||
"tickCount": 5
|
||||
}
|
||||
],
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
"name": "xscale",
|
||||
"type": "linear",
|
||||
"nice": true,
|
||||
"zero": false,
|
||||
"domain": {
|
||||
"data": "facet",
|
||||
"field": "x"
|
||||
|
@ -86,10 +87,10 @@
|
|||
"type": "linear",
|
||||
"range": "height",
|
||||
"nice": true,
|
||||
"zero": true,
|
||||
"zero": false,
|
||||
"domain": {
|
||||
"data": "facet",
|
||||
"field": "p99"
|
||||
"fields": ["p1", "p99"]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -113,12 +114,14 @@
|
|||
"tickOpacity": 0.0,
|
||||
"domainColor": "#727d93",
|
||||
"domainOpacity": 0.1,
|
||||
"format": ".9~s",
|
||||
"tickCount": 5
|
||||
},
|
||||
{
|
||||
"orient": "left",
|
||||
"scale": "yscale",
|
||||
"grid": false,
|
||||
"format": ".9~s",
|
||||
"labelColor": "#727d93",
|
||||
"tickColor": "#fff",
|
||||
"tickOpacity": 0.0,
|
||||
|
|
|
@ -14,6 +14,8 @@ export {
|
|||
errorValueToString,
|
||||
distributionErrorToString,
|
||||
distributionError,
|
||||
declarationArg,
|
||||
declaration,
|
||||
} from "../rescript/TypescriptInterface.gen";
|
||||
export type { errorValue, externalBindings as bindings, jsImports };
|
||||
import {
|
||||
|
@ -185,5 +187,7 @@ function createTsExport(
|
|||
return tag("date", x.value);
|
||||
case "EvTimeDuration":
|
||||
return tag("timeDuration", x.value);
|
||||
case "EvDeclaration":
|
||||
return tag("lambdaDeclaration", x.value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
discreteShape,
|
||||
continuousShape,
|
||||
lambdaValue,
|
||||
lambdaDeclaration,
|
||||
declarationArg,
|
||||
} from "../rescript/TypescriptInterface.gen";
|
||||
import { Distribution } from "./distribution";
|
||||
import { tagged, tag } from "./types";
|
||||
|
@ -63,6 +65,10 @@ export type rescriptExport =
|
|||
| {
|
||||
TAG: 11; // EvTimeDuration
|
||||
_0: number;
|
||||
}
|
||||
| {
|
||||
TAG: 12; // EvDeclaration
|
||||
_0: rescriptLambdaDeclaration;
|
||||
};
|
||||
|
||||
type rescriptDist =
|
||||
|
@ -84,6 +90,23 @@ type rescriptPointSetDist =
|
|||
_0: continuousShape;
|
||||
};
|
||||
|
||||
type rescriptLambdaDeclaration = {
|
||||
readonly fn: lambdaValue;
|
||||
readonly args: rescriptDeclarationArg[];
|
||||
};
|
||||
|
||||
type rescriptDeclarationArg =
|
||||
| {
|
||||
TAG: 0; // Float
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
| {
|
||||
TAG: 1; // Date
|
||||
min: Date;
|
||||
max: Date;
|
||||
};
|
||||
|
||||
export type squiggleExpression =
|
||||
| tagged<"symbol", string>
|
||||
| tagged<"string", string>
|
||||
|
@ -96,6 +119,7 @@ export type squiggleExpression =
|
|||
| tagged<"number", number>
|
||||
| tagged<"date", Date>
|
||||
| tagged<"timeDuration", number>
|
||||
| tagged<"lambdaDeclaration", lambdaDeclaration>
|
||||
| tagged<"record", { [key: string]: squiggleExpression }>;
|
||||
|
||||
export { lambdaValue };
|
||||
|
@ -141,6 +165,22 @@ export function convertRawToTypescript(
|
|||
return tag("date", result._0);
|
||||
case 11: // EvTimeDuration
|
||||
return tag("number", result._0);
|
||||
case 12: // EvDeclaration
|
||||
return tag("lambdaDeclaration", {
|
||||
fn: result._0.fn,
|
||||
args: result._0.args.map(convertDeclaration),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function convertDeclaration(
|
||||
declarationArg: rescriptDeclarationArg
|
||||
): declarationArg {
|
||||
switch (declarationArg.TAG) {
|
||||
case 0: // Float
|
||||
return tag("Float", { min: declarationArg.min, max: declarationArg.max });
|
||||
case 1: // Date
|
||||
return tag("Date", { min: declarationArg.min, max: declarationArg.max });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,9 +8,11 @@ type rec frType =
|
|||
| FRTypeNumber
|
||||
| FRTypeNumeric
|
||||
| FRTypeDistOrNumber
|
||||
| FRTypeLambda
|
||||
| FRTypeRecord(frTypeRecord)
|
||||
| FRTypeArray(array<frType>)
|
||||
| FRTypeOption(frType)
|
||||
| FRTypeArray(frType)
|
||||
| FRTypeString
|
||||
| FRTypeVariant(array<string>)
|
||||
and frTypeRecord = array<frTypeRecordParam>
|
||||
and frTypeRecordParam = (string, frType)
|
||||
|
||||
|
@ -21,9 +23,12 @@ and frTypeRecordParam = (string, frType)
|
|||
type rec frValue =
|
||||
| FRValueNumber(float)
|
||||
| FRValueDist(DistributionTypes.genericDist)
|
||||
| FRValueOption(option<frValue>)
|
||||
| FRValueArray(array<frValue>)
|
||||
| FRValueDistOrNumber(frValueDistOrNumber)
|
||||
| FRValueRecord(frValueRecord)
|
||||
| FRValueLambda(ReducerInterface_ExpressionValue.lambdaValue)
|
||||
| FRValueString(string)
|
||||
| FRValueVariant(string)
|
||||
and frValueRecord = array<frValueRecordParam>
|
||||
and frValueRecordParam = (string, frValue)
|
||||
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
|
||||
|
@ -52,8 +57,10 @@ module FRType = {
|
|||
let input = ((name, frType): frTypeRecordParam) => `${name}: ${toString(frType)}`
|
||||
`record({${r->E.A2.fmap(input)->E.A2.joinWith(", ")}})`
|
||||
}
|
||||
| FRTypeArray(r) => `record(${r->E.A2.fmap(toString)->E.A2.joinWith(", ")})`
|
||||
| FRTypeOption(v) => `option(${toString(v)})`
|
||||
| FRTypeArray(r) => `record(${toString(r)})`
|
||||
| FRTypeLambda => `lambda`
|
||||
| FRTypeString => `string`
|
||||
| FRTypeVariant(_) => "variant"
|
||||
}
|
||||
|
||||
let rec matchWithExpressionValue = (t: t, r: expressionValue): option<frValue> =>
|
||||
|
@ -65,7 +72,11 @@ module FRType = {
|
|||
| (FRTypeDistOrNumber, EvDistribution(f)) => Some(FRValueDistOrNumber(FRValueDist(f)))
|
||||
| (FRTypeNumeric, EvNumber(f)) => Some(FRValueNumber(f))
|
||||
| (FRTypeNumeric, EvDistribution(Symbolic(#Float(f)))) => Some(FRValueNumber(f))
|
||||
| (FRTypeOption(v), _) => Some(FRValueOption(matchWithExpressionValue(v, r)))
|
||||
| (FRTypeLambda, EvLambda(f)) => Some(FRValueLambda(f))
|
||||
| (FRTypeArray(intendedType), EvArray(elements)) => {
|
||||
let el = elements->E.A2.fmap(matchWithExpressionValue(intendedType))
|
||||
E.A.O.openIfAllSome(el)->E.O2.fmap(r => FRValueArray(r))
|
||||
}
|
||||
| (FRTypeRecord(recordParams), EvRecord(record)) => {
|
||||
let getAndMatch = (name, input) =>
|
||||
E.Dict.get(record, name)->E.O.bind(matchWithExpressionValue(input))
|
||||
|
@ -80,6 +91,23 @@ module FRType = {
|
|||
| _ => None
|
||||
}
|
||||
|
||||
let rec matchReverse = (e: frValue): expressionValue =>
|
||||
switch e {
|
||||
| FRValueNumber(f) => EvNumber(f)
|
||||
| FRValueDistOrNumber(FRValueNumber(n)) => EvNumber(n)
|
||||
| FRValueDistOrNumber(FRValueDist(n)) => EvDistribution(n)
|
||||
| FRValueDist(dist) => EvDistribution(dist)
|
||||
| FRValueArray(elements) => EvArray(elements->E.A2.fmap(matchReverse))
|
||||
| FRValueRecord(frValueRecord) => {
|
||||
let record =
|
||||
frValueRecord->E.A2.fmap(((name, value)) => (name, matchReverse(value)))->E.Dict.fromArray
|
||||
EvRecord(record)
|
||||
}
|
||||
| FRValueLambda(l) => EvLambda(l)
|
||||
| FRValueString(string) => EvString(string)
|
||||
| FRValueVariant(string) => EvString(string)
|
||||
}
|
||||
|
||||
let matchWithExpressionValueArray = (inputs: array<t>, args: array<expressionValue>): option<
|
||||
array<frValue>,
|
||||
> => {
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
||||
|
||||
type rec frType =
|
||||
| FRTypeNumber
|
||||
| FRTypeNumeric
|
||||
| FRTypeDistOrNumber
|
||||
| FRTypeRecord(frTypeRecord)
|
||||
| FRTypeArray(array<frType>)
|
||||
| FRTypeOption(frType)
|
||||
and frTypeRecord = array<frTypeRecordParam>
|
||||
and frTypeRecordParam = (string, frType)
|
||||
|
||||
type rec frValue =
|
||||
| FRValueNumber(float)
|
||||
| FRValueDist(DistributionTypes.genericDist)
|
||||
| FRValueOption(option<frValue>)
|
||||
| FRValueDistOrNumber(frValueDistOrNumber)
|
||||
| FRValueRecord(frValueRecord)
|
||||
and frValueRecord = array<frValueRecordParam>
|
||||
and frValueRecordParam = (string, frValue)
|
||||
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
|
||||
|
||||
type fnDefinition = {
|
||||
name: string,
|
||||
inputs: array<frType>,
|
||||
run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
|
||||
}
|
||||
|
||||
type function = {
|
||||
name: string,
|
||||
definitions: array<fnDefinition>,
|
||||
}
|
||||
|
||||
type registry = array<function>
|
||||
|
||||
// Note: The function "name" is just used for documentation purposes
|
||||
module Function: {
|
||||
type t = function
|
||||
let make: (~name: string, ~definitions: array<fnDefinition>) => t
|
||||
}
|
||||
|
||||
module FnDefinition: {
|
||||
type t = fnDefinition
|
||||
let make: (
|
||||
~name: string,
|
||||
~inputs: array<frType>,
|
||||
~run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
|
||||
) => t
|
||||
}
|
||||
|
||||
module Registry: {
|
||||
let matchAndRun: (
|
||||
~registry: registry,
|
||||
~fnName: string,
|
||||
~args: array<expressionValue>,
|
||||
~env: QuriSquiggleLang.DistributionOperation.env,
|
||||
) => option<result<expressionValue, string>>
|
||||
}
|
|
@ -19,6 +19,12 @@ module Prepare = {
|
|||
| [FRValueRecord([(_, n1), (_, n2)])] => Ok([n1, n2])
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
|
||||
let toArgs = (inputs: ts): result<ts, err> =>
|
||||
switch inputs {
|
||||
| [FRValueRecord(args)] => args->E.A2.fmap(((_, b)) => b)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +36,13 @@ module Prepare = {
|
|||
}
|
||||
}
|
||||
|
||||
let twoNumbers = (values: ts): result<(float, float), err> => {
|
||||
switch values {
|
||||
| [FRValueNumber(a1), FRValueNumber(a2)] => Ok(a1, a2)
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
}
|
||||
|
||||
let oneDistOrNumber = (values: ts): result<frValueDistOrNumber, err> => {
|
||||
switch values {
|
||||
| [FRValueDistOrNumber(a1)] => Ok(a1)
|
||||
|
|
|
@ -3,7 +3,42 @@ open FunctionRegistry_Helpers
|
|||
|
||||
let twoArgs = E.Tuple2.toFnCall
|
||||
|
||||
module Declaration = {
|
||||
let frType = FRTypeRecord([
|
||||
("fn", FRTypeLambda),
|
||||
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
|
||||
])
|
||||
|
||||
let fromExpressionValue = (e: frValue): result<expressionValue, string> => {
|
||||
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs([e]) {
|
||||
| Ok([FRValueLambda(lambda), FRValueArray(inputs)]) => {
|
||||
open FunctionRegistry_Helpers.Prepare
|
||||
let getMinMax = arg =>
|
||||
ToValueArray.Record.toArgs([arg])
|
||||
->E.R.bind(ToValueTuple.twoNumbers)
|
||||
->E.R2.fmap(((min, max)) => Declaration.ContinuousFloatArg.make(min, max))
|
||||
inputs
|
||||
->E.A2.fmap(getMinMax)
|
||||
->E.A.R.firstErrorOrOpen
|
||||
->E.R2.fmap(args => ReducerInterface_ExpressionValue.EvDeclaration(
|
||||
Declaration.make(lambda, args),
|
||||
))
|
||||
}
|
||||
| Error(r) => Error(r)
|
||||
| Ok(_) => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let registry = [
|
||||
Function.make(
|
||||
~name="FnMake",
|
||||
~definitions=[
|
||||
FnDefinition.make(~name="declareFn", ~inputs=[Declaration.frType], ~run=(inputs, _) => {
|
||||
inputs->E.A.unsafe_get(0)->Declaration.fromExpressionValue
|
||||
}),
|
||||
],
|
||||
),
|
||||
Function.make(
|
||||
~name="Normal",
|
||||
~definitions=[
|
||||
|
|
|
@ -22,6 +22,7 @@ type rec expressionValue =
|
|||
| EvSymbol(string)
|
||||
| EvDate(Js.Date.t)
|
||||
| EvTimeDuration(float)
|
||||
| EvDeclaration(lambdaDeclaration)
|
||||
and record = Js.Dict.t<expressionValue>
|
||||
and externalBindings = record
|
||||
and lambdaValue = {
|
||||
|
@ -29,6 +30,7 @@ and lambdaValue = {
|
|||
context: externalBindings,
|
||||
body: internalCode,
|
||||
}
|
||||
and lambdaDeclaration = Declaration.declaration<lambdaValue>
|
||||
|
||||
@genType
|
||||
let defaultExternalBindings: externalBindings = Js.Dict.empty()
|
||||
|
@ -55,6 +57,7 @@ let rec toString = aValue =>
|
|||
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||
| EvDate(date) => DateTime.Date.toString(date)
|
||||
| EvTimeDuration(t) => DateTime.Duration.toString(t)
|
||||
| EvDeclaration(d) => Declaration.toString(d, r => toString(EvLambda(r)))
|
||||
}
|
||||
and toStringRecord = aRecord => {
|
||||
let pairs =
|
||||
|
@ -79,6 +82,7 @@ let toStringWithType = aValue =>
|
|||
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
||||
| EvDate(_) => `Date::${toString(aValue)}`
|
||||
| EvTimeDuration(_) => `Date::${toString(aValue)}`
|
||||
| EvDeclaration(_) => `Declaration::${toString(aValue)}`
|
||||
}
|
||||
|
||||
let argsToString = (args: array<expressionValue>): string => {
|
||||
|
@ -124,6 +128,7 @@ type expressionValueType =
|
|||
| EvtSymbol
|
||||
| EvtDate
|
||||
| EvtTimeDuration
|
||||
| EvtDeclaration
|
||||
|
||||
type functionCallSignature = CallSignature(string, array<expressionValueType>)
|
||||
type functionDefinitionSignature =
|
||||
|
@ -143,6 +148,7 @@ let valueToValueType = value =>
|
|||
| EvSymbol(_) => EvtSymbol
|
||||
| EvDate(_) => EvtDate
|
||||
| EvTimeDuration(_) => EvtTimeDuration
|
||||
| EvDeclaration(_) => EvtDeclaration
|
||||
}
|
||||
|
||||
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
|
||||
|
@ -164,6 +170,7 @@ let valueTypeToString = (valueType: expressionValueType): string =>
|
|||
| EvtSymbol => `Symbol`
|
||||
| EvtDate => `Date`
|
||||
| EvtTimeDuration => `Duration`
|
||||
| EvtDeclaration => `Declaration`
|
||||
}
|
||||
|
||||
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
|
||||
|
|
|
@ -76,6 +76,9 @@ let distributionErrorToString = DistributionTypes.Error.toString
|
|||
@genType
|
||||
type lambdaValue = ReducerInterface_ExpressionValue.lambdaValue
|
||||
|
||||
@genType
|
||||
type lambdaDeclaration = ReducerInterface_ExpressionValue.lambdaDeclaration
|
||||
|
||||
@genType
|
||||
let defaultSamplingEnv = DistributionOperation.defaultEnv
|
||||
|
||||
|
@ -87,3 +90,9 @@ let defaultEnvironment = ReducerInterface_ExpressionValue.defaultEnvironment
|
|||
|
||||
@genType
|
||||
let foreignFunctionInterface = Reducer.foreignFunctionInterface
|
||||
|
||||
@genType
|
||||
type declarationArg = Declaration.arg
|
||||
|
||||
@genType
|
||||
type declaration<'a> = Declaration.declaration<'a>
|
||||
|
|
42
packages/squiggle-lang/src/rescript/Utility/Declaration.res
Normal file
42
packages/squiggle-lang/src/rescript/Utility/Declaration.res
Normal file
|
@ -0,0 +1,42 @@
|
|||
@genType
|
||||
type arg = Float({min: float, max: float}) | Date({min: Js.Date.t, max: Js.Date.t})
|
||||
|
||||
@genType
|
||||
type declaration<'a> = {
|
||||
fn: 'a,
|
||||
args: array<arg>,
|
||||
}
|
||||
|
||||
module ContinuousFloatArg = {
|
||||
let make = (min: float, max: float): arg => {
|
||||
Float({min: min, max: max})
|
||||
}
|
||||
}
|
||||
|
||||
module ContinuousTimeArg = {
|
||||
let make = (min: Js.Date.t, max: Js.Date.t): arg => {
|
||||
Date({min: min, max: max})
|
||||
}
|
||||
}
|
||||
|
||||
module Arg = {
|
||||
let toString = (arg: arg) => {
|
||||
switch arg {
|
||||
| Float({min, max}) =>
|
||||
`Float({min: ${E.Float.with2DigitsPrecision(min)}, max: ${E.Float.with2DigitsPrecision(
|
||||
max,
|
||||
)}})`
|
||||
| Date({min, max}) =>
|
||||
`Date({min: ${DateTime.Date.toString(min)}, max: ${DateTime.Date.toString(max)}})`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let make = (fn: 'a, args: array<arg>): declaration<'a> => {
|
||||
{fn: fn, args: args}
|
||||
}
|
||||
|
||||
let toString = (r: declaration<'a>, fnToString): string => {
|
||||
let args = r.args->E.A2.fmap(Arg.toString) |> E.A.joinWith(", ")
|
||||
return`fn: ${fnToString(r.fn)}, args: [${args}]`
|
||||
}
|
|
@ -870,4 +870,5 @@ module Dict = {
|
|||
type t<'a> = Js.Dict.t<'a>
|
||||
let get = Js.Dict.get
|
||||
let keys = Js.Dict.keys
|
||||
let fromArray = Js.Dict.fromArray
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user