From 6468845255e5516d4567f9bef48d815549b20910 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 28 Aug 2019 14:53:55 +0100 Subject: chore: Change file layout to match repository instead of gist --- README.md | 40 ++++++ default.nix | 295 +++++++++++++++++++++++++++++++++++++++++ screenshots/enums.png | Bin 0 -> 41305 bytes screenshots/functions.png | Bin 0 -> 32907 bytes screenshots/nested-structs.png | Bin 0 -> 70264 bytes screenshots/simple.png | Bin 0 -> 43010 bytes screenshots/structs.png | Bin 0 -> 69499 bytes tests.nix | 90 +++++++++++++ yants.md | 38 ------ yants.nix | 295 ----------------------------------------- z-enums.png | Bin 41305 -> 0 bytes z-functions.png | Bin 32907 -> 0 bytes z-nested-structs.png | Bin 70264 -> 0 bytes z-simple.png | Bin 43010 -> 0 bytes z-structs.png | Bin 69499 -> 0 bytes z-yants-tests.nix | 90 ------------- 16 files changed, 425 insertions(+), 423 deletions(-) create mode 100644 README.md create mode 100644 default.nix create mode 100644 screenshots/enums.png create mode 100644 screenshots/functions.png create mode 100644 screenshots/nested-structs.png create mode 100644 screenshots/simple.png create mode 100644 screenshots/structs.png create mode 100644 tests.nix delete mode 100644 yants.md delete mode 100644 yants.nix delete mode 100644 z-enums.png delete mode 100644 z-functions.png delete mode 100644 z-nested-structs.png delete mode 100644 z-simple.png delete mode 100644 z-structs.png delete mode 100644 z-yants-tests.nix diff --git a/README.md b/README.md new file mode 100644 index 0000000000..55ea764d8f --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +yants +===== + +This is a tiny type-checker for data in Nix, written in Nix. + +Features: + +* Checking of primitive types (`int`, `string` etc.) +* Checking polymorphic types (`option`, `list`, `either`) +* Defining & checking struct/record types +* Defining & matching enum types +* Defining & matching sum types +* Defining function signatures (including curried functions) +* Types are composable! `option string`! `list (either int (option float))`! +* Type errors also compose! + +Lacking: + +* Any kind of inference +* Convenient syntax for attribute-set function signatures + +## Primitives & simple polymorphism + +![simple](screenshots/simple.png) + +## Structs + +![structs](screenshots/structs.png) + +## Nested structs! + +![nested structs](screenshots/nested-structs.png) + +## Enums! + +![enums](screenshots/enums.png) + +## Functions! + +![functions](screenshots/functions.png) diff --git a/default.nix b/default.nix new file mode 100644 index 0000000000..17564b61b9 --- /dev/null +++ b/default.nix @@ -0,0 +1,295 @@ +# Copyright 2019 Google LLC +# SPDX-License-Identifier: Apache-2.0 +# +# Provides a "type-system" for Nix that provides various primitive & +# polymorphic types as well as the ability to define & check records. +# +# All types (should) compose as expected. + +{ lib ? (import {}).lib }: + +with builtins; let + prettyPrint = lib.generators.toPretty {}; + + # typedef' :: struct { + # name = string; + # checkType = function; (a -> result) + # checkToBool = option function; (result -> bool) + # toError = option function; (a -> result -> string) + # def = option any; + # match = option function; + # } -> type + # -> (a -> b) + # -> (b -> bool) + # -> (a -> b -> string) + # -> type + # + # This function creates an attribute set that acts as a type. + # + # It receives a type name, a function that is used to perform a + # check on an arbitrary value, a function that can translate the + # return of that check to a boolean that informs whether the value + # is type-conformant, and a function that can construct error + # messages from the check result. + # + # This function is the low-level primitive used to create types. For + # many cases the higher-level 'typedef' function is more appropriate. + typedef' = { name, checkType + , checkToBool ? (result: result.ok) + , toError ? (_: result: result.err) + , def ? null + , match ? null }: { + inherit name checkToBool toError; + + # check :: a -> bool + # + # This function is used to determine whether a given type is + # conformant. + check = value: checkToBool (checkType value); + + # checkType :: a -> struct { ok = bool; err = option string; } + # + # This function checks whether the passed value is type conformant + # and returns an optional type error string otherwise. + inherit checkType; + + # __functor :: a -> a + # + # This function checks whether the passed value is type conformant + # and throws an error if it is not. + # + # The name of this function is a special attribute in Nix that + # makes it possible to execute a type attribute set like a normal + # function. + __functor = self: value: + let result = self.checkType value; + in if checkToBool result then value + else throw (toError value result); + }; + + typeError = type: val: + "expected type '${type}', but value '${prettyPrint val}' is of type '${typeOf val}'"; + + # typedef :: string -> (a -> bool) -> type + # + # typedef is the simplified version of typedef' which uses a default + # error message constructor. + typedef = name: check: typedef' { + inherit name; + checkType = check; + checkToBool = r: r; + toError = value: _result: typeError name value; + }; + + checkEach = name: t: l: foldl' (acc: e: + let res = t.checkType e; + isT = t.checkToBool res; + in { + ok = acc.ok && isT; + err = if isT + then acc.err + else acc.err + "${prettyPrint e}: ${t.toError e res}\n"; + }) { ok = true; err = "expected type ${name}, but found:\n"; } l; +in lib.fix (self: { + # Primitive types + any = typedef "any" (_: true); + int = typedef "int" isInt; + bool = typedef "bool" isBool; + float = typedef "float" isFloat; + string = typedef "string" isString; + drv = typedef "derivation" (x: isAttrs x && x ? "type" && x.type == "derivation"); + function = typedef "function" (x: isFunction x || (isAttrs x && x ? "__functor" + && isFunction x.__functor)); + + # Type for types themselves. Useful when defining polymorphic types. + type = typedef "type" (x: + isAttrs x + && hasAttr "name" x && self.string.check x.name + && hasAttr "checkType" x && self.function.check x.checkType + && hasAttr "checkToBool" x && self.function.check x.checkToBool + && hasAttr "toError" x && self.function.check x.toError + ); + + # Polymorphic types + option = t: typedef' rec { + name = "option<${t.name}>"; + checkType = v: + let res = t.checkType v; + in { + ok = isNull v || (self.type t).checkToBool res; + err = "expected type ${name}, but value does not conform to '${t.name}': " + + t.toError v res; + }; + }; + + either = t1: t2: typedef "either<${t1.name},${t2.name}>" + (x: (self.type t1).check x || (self.type t2).check x); + + list = t: typedef' rec { + name = "list<${t.name}>"; + + checkType = v: if isList v + then checkEach name (self.type t) v + else { + ok = false; + err = typeError name v; + }; + }; + + attrs = t: typedef' rec { + name = "attrs<${t.name}>"; + + checkType = v: if isAttrs v + then checkEach name (self.type t) (attrValues v) + else { + ok = false; + err = typeError name v; + }; + }; + + # Structs / record types + # + # Checks that all fields match their declared types, no optional + # fields are missing and no unexpected fields occur in the struct. + # + # Anonymous structs are supported (e.g. for nesting) by omitting the + # name. + # + # TODO: Support open records? + struct = + # Struct checking is more involved than the simpler types above. + # To make the actual type definition more readable, several + # helpers are defined below. + let + # checkField checks an individual field of the struct against + # its definition and creates a typecheck result. These results + # are aggregated during the actual checking. + checkField = def: name: value: let result = def.checkType value; in rec { + ok = def.checkToBool result; + err = if !ok && isNull value + then "missing required ${def.name} field '${name}'\n" + else "field '${name}': ${def.toError value result}\n"; + }; + + # checkExtraneous determines whether a (closed) struct contains + # any fields that are not part of the definition. + checkExtraneous = def: has: acc: + if (length has) == 0 then acc + else if (hasAttr (head has) def) + then checkExtraneous def (tail has) acc + else checkExtraneous def (tail has) { + ok = false; + err = acc.err + "unexpected struct field '${head has}'\n"; + }; + + # checkStruct combines all structure checks and creates one + # typecheck result from them + checkStruct = def: value: + let + init = { ok = true; err = ""; }; + extraneous = checkExtraneous def (attrNames value) init; + + checkedFields = map (n: + let v = if hasAttr n value then value."${n}" else null; + in checkField def."${n}" n v) (attrNames def); + + combined = foldl' (acc: res: { + ok = acc.ok && res.ok; + err = if !res.ok then acc.err + res.err else acc.err; + }) init checkedFields; + in { + ok = combined.ok && extraneous.ok; + err = combined.err + extraneous.err; + }; + + struct' = name: def: typedef' { + inherit name def; + checkType = value: if isAttrs value + then (checkStruct (self.attrs self.type def) value) + else { ok = false; err = typeError name value; }; + + toError = _: result: "expected '${name}'-struct, but found:\n" + result.err; + }; + in arg: if isString arg then (struct' arg) else (struct' "anon" arg); + + # Enums & pattern matching + enum = + let + plain = name: def: typedef' { + inherit name def; + + checkType = (x: isString x && elem x def); + checkToBool = x: x; + toError = value: _: "'${prettyPrint value} is not a member of enum ${name}"; + }; + enum' = name: def: lib.fix (e: (plain name def) // { + match = x: actions: deepSeq (map e (attrNames actions)) ( + let + actionKeys = attrNames actions; + missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [] def; + in if (length missing) > 0 + then throw "Missing match action for members: ${prettyPrint missing}" + else actions."${e x}"); + }); + in arg: if isString arg then (enum' arg) else (enum' "anon" arg); + + # Sum types + # + # The representation of a sum type is an attribute set with only one + # value, where the key of the value denotes the variant of the type. + sum = + let + plain = name: def: typedef' { + inherit name def; + checkType = (x: + let variant = elemAt (attrNames x) 0; + in if isAttrs x && length (attrNames x) == 1 && hasAttr variant def + then let t = def."${variant}"; + v = x."${variant}"; + res = t.checkType v; + in if t.checkToBool res + then { ok = true; } + else { + ok = false; + err = "while checking '${name}' variant '${variant}': " + + t.toError v res; + } + else { ok = false; err = typeError name x; } + ); + }; + sum' = name: def: lib.fix (s: (plain name def) // { + match = x: actions: + let variant = deepSeq (s x) (elemAt (attrNames x) 0); + actionKeys = attrNames actions; + defKeys = attrNames def; + missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [] defKeys; + in if (length missing) > 0 + then throw "Missing match action for variants: ${prettyPrint missing}" + else actions."${variant}" x."${variant}"; + }); + in arg: if isString arg then (sum' arg) else (sum' "anon" arg); + + # Typed function definitions + # + # These definitions wrap the supplied function in type-checking + # forms that are evaluated when the function is called. + # + # Note that typed functions themselves are not types and can not be + # used to check values for conformity. + defun = + let + mkFunc = sig: f: { + inherit sig; + __toString = self: foldl' (s: t: "${s} -> ${t.name}") + "λ :: ${(head self.sig).name}" (tail self.sig); + __functor = _: f; + }; + + defun' = sig: func: if length sig > 2 + then mkFunc sig (x: defun' (tail sig) (func ((head sig) x))) + else mkFunc sig (x: ((head (tail sig)) (func ((head sig) x)))); + + in sig: func: if length sig < 2 + then (throw "Signature must at least have two types (a -> b)") + else defun' sig func; +}) diff --git a/screenshots/enums.png b/screenshots/enums.png new file mode 100644 index 0000000000..71673e7ab6 Binary files /dev/null and b/screenshots/enums.png differ diff --git a/screenshots/functions.png b/screenshots/functions.png new file mode 100644 index 0000000000..30ed50f832 Binary files /dev/null and b/screenshots/functions.png differ diff --git a/screenshots/nested-structs.png b/screenshots/nested-structs.png new file mode 100644 index 0000000000..6b03ed65ce Binary files /dev/null and b/screenshots/nested-structs.png differ diff --git a/screenshots/simple.png b/screenshots/simple.png new file mode 100644 index 0000000000..05a302cc6b Binary files /dev/null and b/screenshots/simple.png differ diff --git a/screenshots/structs.png b/screenshots/structs.png new file mode 100644 index 0000000000..fcbcf6415f Binary files /dev/null and b/screenshots/structs.png differ diff --git a/tests.nix b/tests.nix new file mode 100644 index 0000000000..344f2c1f59 --- /dev/null +++ b/tests.nix @@ -0,0 +1,90 @@ +with builtins; +with (import ./default.nix {}); + +# Note: Derivations are not included in the tests below as they cause +# issues with deepSeq. + +deepSeq rec { + # Test that all primitive types match + primitives = [ + (int 15) + (bool false) + (float 13.37) + (string "Hello!") + (function (x: x * 2)) + ]; + + # Test that polymorphic types work as intended + poly = [ + (option int null) + (list string [ "foo" "bar" ]) + (either int float 42) + ]; + + # Test that structures work as planned. + person = struct "person" { + name = string; + age = int; + + contact = option (struct { + email = string; + phone = option string; + }); + }; + + testPerson = person { + name = "Brynhjulf"; + age = 42; + contact.email = "brynhjulf@yants.nix"; + }; + + # Test enum definitions & matching + colour = enum "colour" [ "red" "blue" "green" ]; + testMatch = colour.match "red" { + red = "It is in fact red!"; + blue = throw "It should not be blue!"; + green = throw "It should not be green!"; + }; + + # Test sum type definitions + creature = sum "creature" { + human = struct { + name = string; + age = option int; + }; + + pet = enum "pet" [ "dog" "lizard" "cat" ]; + }; + + testSum = creature { + human = { + name = "Brynhjulf"; + age = 42; + }; + }; + + testSumMatch = creature.match testSum { + human = v: "It's a human named ${v.name}"; + pet = v: throw "It's not supposed to be a pet!"; + }; + + # Test curried function definitions + func = defun [ string int string ] + (name: age: "${name} is ${toString age} years old"); + + testFunc = func "Brynhjulf" 42; + + # Test that all types are types. + testTypes = map type [ + any bool drv float int string + + (attrs int) + (either int string) + (enum [ "foo" "bar" ]) + (list string) + (option int) + (option (list string)) + (struct { a = int; b = option string; }) + (sum { a = int; b = option string; }) + ]; +} "All tests passed!\n" diff --git a/yants.md b/yants.md deleted file mode 100644 index e10e120f08..0000000000 --- a/yants.md +++ /dev/null @@ -1,38 +0,0 @@ -yants -===== - -This is a tiny type-checker for data in Nix, written in Nix. - -Features: - -* Checking of primitive types (`int`, `string` etc.) -* Checking polymorphic types (`option`, `list`, `either`) -* Defining & checking struct/record types -* Defining & matching enum types -* Defining function signatures (including curried functions) -* Types are composable! `option string`! `list (either int (option float))`! - -Lacking: - -* Any kind of inference (Nix's reflection ability is not strong enough) -* Convenient syntax for attribute-set function signatures - -## Primitives & simple polymorphism - -![simple](https://gist.githubusercontent.com/tazjin/ad6d48bc2416335acc5da4a197eb9ddc/raw/d7b1fa0a511ae40f0831b369df4b97103441c7e5/z-simple.png) - -## Structs - -![structs](https://gist.githubusercontent.com/tazjin/ad6d48bc2416335acc5da4a197eb9ddc/raw/d7a7cff3639115538a5085561bedf11cb36d04e7/z-structs.png) - -## Nested structs! - -![nested structs](https://gist.githubusercontent.com/tazjin/ad6d48bc2416335acc5da4a197eb9ddc/raw/d7b1fa0a511ae40f0831b369df4b97103441c7e5/z-nested-structs.png) - -## Enums! - -![enums](https://gist.githubusercontent.com/tazjin/ad6d48bc2416335acc5da4a197eb9ddc/raw/b435b5996a176a9e824c42da4713a1d30f261338/z-enums.png) - -## Functions! - -![functions](https://gist.githubusercontent.com/tazjin/ad6d48bc2416335acc5da4a197eb9ddc/raw/ccece1eb9a1cb3b1add1ba1a1f65df3adca64a8f/z-functions.png) diff --git a/yants.nix b/yants.nix deleted file mode 100644 index 17564b61b9..0000000000 --- a/yants.nix +++ /dev/null @@ -1,295 +0,0 @@ -# Copyright 2019 Google LLC -# SPDX-License-Identifier: Apache-2.0 -# -# Provides a "type-system" for Nix that provides various primitive & -# polymorphic types as well as the ability to define & check records. -# -# All types (should) compose as expected. - -{ lib ? (import {}).lib }: - -with builtins; let - prettyPrint = lib.generators.toPretty {}; - - # typedef' :: struct { - # name = string; - # checkType = function; (a -> result) - # checkToBool = option function; (result -> bool) - # toError = option function; (a -> result -> string) - # def = option any; - # match = option function; - # } -> type - # -> (a -> b) - # -> (b -> bool) - # -> (a -> b -> string) - # -> type - # - # This function creates an attribute set that acts as a type. - # - # It receives a type name, a function that is used to perform a - # check on an arbitrary value, a function that can translate the - # return of that check to a boolean that informs whether the value - # is type-conformant, and a function that can construct error - # messages from the check result. - # - # This function is the low-level primitive used to create types. For - # many cases the higher-level 'typedef' function is more appropriate. - typedef' = { name, checkType - , checkToBool ? (result: result.ok) - , toError ? (_: result: result.err) - , def ? null - , match ? null }: { - inherit name checkToBool toError; - - # check :: a -> bool - # - # This function is used to determine whether a given type is - # conformant. - check = value: checkToBool (checkType value); - - # checkType :: a -> struct { ok = bool; err = option string; } - # - # This function checks whether the passed value is type conformant - # and returns an optional type error string otherwise. - inherit checkType; - - # __functor :: a -> a - # - # This function checks whether the passed value is type conformant - # and throws an error if it is not. - # - # The name of this function is a special attribute in Nix that - # makes it possible to execute a type attribute set like a normal - # function. - __functor = self: value: - let result = self.checkType value; - in if checkToBool result then value - else throw (toError value result); - }; - - typeError = type: val: - "expected type '${type}', but value '${prettyPrint val}' is of type '${typeOf val}'"; - - # typedef :: string -> (a -> bool) -> type - # - # typedef is the simplified version of typedef' which uses a default - # error message constructor. - typedef = name: check: typedef' { - inherit name; - checkType = check; - checkToBool = r: r; - toError = value: _result: typeError name value; - }; - - checkEach = name: t: l: foldl' (acc: e: - let res = t.checkType e; - isT = t.checkToBool res; - in { - ok = acc.ok && isT; - err = if isT - then acc.err - else acc.err + "${prettyPrint e}: ${t.toError e res}\n"; - }) { ok = true; err = "expected type ${name}, but found:\n"; } l; -in lib.fix (self: { - # Primitive types - any = typedef "any" (_: true); - int = typedef "int" isInt; - bool = typedef "bool" isBool; - float = typedef "float" isFloat; - string = typedef "string" isString; - drv = typedef "derivation" (x: isAttrs x && x ? "type" && x.type == "derivation"); - function = typedef "function" (x: isFunction x || (isAttrs x && x ? "__functor" - && isFunction x.__functor)); - - # Type for types themselves. Useful when defining polymorphic types. - type = typedef "type" (x: - isAttrs x - && hasAttr "name" x && self.string.check x.name - && hasAttr "checkType" x && self.function.check x.checkType - && hasAttr "checkToBool" x && self.function.check x.checkToBool - && hasAttr "toError" x && self.function.check x.toError - ); - - # Polymorphic types - option = t: typedef' rec { - name = "option<${t.name}>"; - checkType = v: - let res = t.checkType v; - in { - ok = isNull v || (self.type t).checkToBool res; - err = "expected type ${name}, but value does not conform to '${t.name}': " - + t.toError v res; - }; - }; - - either = t1: t2: typedef "either<${t1.name},${t2.name}>" - (x: (self.type t1).check x || (self.type t2).check x); - - list = t: typedef' rec { - name = "list<${t.name}>"; - - checkType = v: if isList v - then checkEach name (self.type t) v - else { - ok = false; - err = typeError name v; - }; - }; - - attrs = t: typedef' rec { - name = "attrs<${t.name}>"; - - checkType = v: if isAttrs v - then checkEach name (self.type t) (attrValues v) - else { - ok = false; - err = typeError name v; - }; - }; - - # Structs / record types - # - # Checks that all fields match their declared types, no optional - # fields are missing and no unexpected fields occur in the struct. - # - # Anonymous structs are supported (e.g. for nesting) by omitting the - # name. - # - # TODO: Support open records? - struct = - # Struct checking is more involved than the simpler types above. - # To make the actual type definition more readable, several - # helpers are defined below. - let - # checkField checks an individual field of the struct against - # its definition and creates a typecheck result. These results - # are aggregated during the actual checking. - checkField = def: name: value: let result = def.checkType value; in rec { - ok = def.checkToBool result; - err = if !ok && isNull value - then "missing required ${def.name} field '${name}'\n" - else "field '${name}': ${def.toError value result}\n"; - }; - - # checkExtraneous determines whether a (closed) struct contains - # any fields that are not part of the definition. - checkExtraneous = def: has: acc: - if (length has) == 0 then acc - else if (hasAttr (head has) def) - then checkExtraneous def (tail has) acc - else checkExtraneous def (tail has) { - ok = false; - err = acc.err + "unexpected struct field '${head has}'\n"; - }; - - # checkStruct combines all structure checks and creates one - # typecheck result from them - checkStruct = def: value: - let - init = { ok = true; err = ""; }; - extraneous = checkExtraneous def (attrNames value) init; - - checkedFields = map (n: - let v = if hasAttr n value then value."${n}" else null; - in checkField def."${n}" n v) (attrNames def); - - combined = foldl' (acc: res: { - ok = acc.ok && res.ok; - err = if !res.ok then acc.err + res.err else acc.err; - }) init checkedFields; - in { - ok = combined.ok && extraneous.ok; - err = combined.err + extraneous.err; - }; - - struct' = name: def: typedef' { - inherit name def; - checkType = value: if isAttrs value - then (checkStruct (self.attrs self.type def) value) - else { ok = false; err = typeError name value; }; - - toError = _: result: "expected '${name}'-struct, but found:\n" + result.err; - }; - in arg: if isString arg then (struct' arg) else (struct' "anon" arg); - - # Enums & pattern matching - enum = - let - plain = name: def: typedef' { - inherit name def; - - checkType = (x: isString x && elem x def); - checkToBool = x: x; - toError = value: _: "'${prettyPrint value} is not a member of enum ${name}"; - }; - enum' = name: def: lib.fix (e: (plain name def) // { - match = x: actions: deepSeq (map e (attrNames actions)) ( - let - actionKeys = attrNames actions; - missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [] def; - in if (length missing) > 0 - then throw "Missing match action for members: ${prettyPrint missing}" - else actions."${e x}"); - }); - in arg: if isString arg then (enum' arg) else (enum' "anon" arg); - - # Sum types - # - # The representation of a sum type is an attribute set with only one - # value, where the key of the value denotes the variant of the type. - sum = - let - plain = name: def: typedef' { - inherit name def; - checkType = (x: - let variant = elemAt (attrNames x) 0; - in if isAttrs x && length (attrNames x) == 1 && hasAttr variant def - then let t = def."${variant}"; - v = x."${variant}"; - res = t.checkType v; - in if t.checkToBool res - then { ok = true; } - else { - ok = false; - err = "while checking '${name}' variant '${variant}': " - + t.toError v res; - } - else { ok = false; err = typeError name x; } - ); - }; - sum' = name: def: lib.fix (s: (plain name def) // { - match = x: actions: - let variant = deepSeq (s x) (elemAt (attrNames x) 0); - actionKeys = attrNames actions; - defKeys = attrNames def; - missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [] defKeys; - in if (length missing) > 0 - then throw "Missing match action for variants: ${prettyPrint missing}" - else actions."${variant}" x."${variant}"; - }); - in arg: if isString arg then (sum' arg) else (sum' "anon" arg); - - # Typed function definitions - # - # These definitions wrap the supplied function in type-checking - # forms that are evaluated when the function is called. - # - # Note that typed functions themselves are not types and can not be - # used to check values for conformity. - defun = - let - mkFunc = sig: f: { - inherit sig; - __toString = self: foldl' (s: t: "${s} -> ${t.name}") - "λ :: ${(head self.sig).name}" (tail self.sig); - __functor = _: f; - }; - - defun' = sig: func: if length sig > 2 - then mkFunc sig (x: defun' (tail sig) (func ((head sig) x))) - else mkFunc sig (x: ((head (tail sig)) (func ((head sig) x)))); - - in sig: func: if length sig < 2 - then (throw "Signature must at least have two types (a -> b)") - else defun' sig func; -}) diff --git a/z-enums.png b/z-enums.png deleted file mode 100644 index 71673e7ab6..0000000000 Binary files a/z-enums.png and /dev/null differ diff --git a/z-functions.png b/z-functions.png deleted file mode 100644 index 30ed50f832..0000000000 Binary files a/z-functions.png and /dev/null differ diff --git a/z-nested-structs.png b/z-nested-structs.png deleted file mode 100644 index 6b03ed65ce..0000000000 Binary files a/z-nested-structs.png and /dev/null differ diff --git a/z-simple.png b/z-simple.png deleted file mode 100644 index 05a302cc6b..0000000000 Binary files a/z-simple.png and /dev/null differ diff --git a/z-structs.png b/z-structs.png deleted file mode 100644 index fcbcf6415f..0000000000 Binary files a/z-structs.png and /dev/null differ diff --git a/z-yants-tests.nix b/z-yants-tests.nix deleted file mode 100644 index e1e521c5ab..0000000000 --- a/z-yants-tests.nix +++ /dev/null @@ -1,90 +0,0 @@ -with builtins; -with (import ./yants.nix {}); - -# Note: Derivations are not included in the tests below as they cause -# issues with deepSeq. - -deepSeq rec { - # Test that all primitive types match - primitives = [ - (int 15) - (bool false) - (float 13.37) - (string "Hello!") - (function (x: x * 2)) - ]; - - # Test that polymorphic types work as intended - poly = [ - (option int null) - (list string [ "foo" "bar" ]) - (either int float 42) - ]; - - # Test that structures work as planned. - person = struct "person" { - name = string; - age = int; - - contact = option (struct { - email = string; - phone = option string; - }); - }; - - testPerson = person { - name = "Brynhjulf"; - age = 42; - contact.email = "brynhjulf@yants.nix"; - }; - - # Test enum definitions & matching - colour = enum "colour" [ "red" "blue" "green" ]; - testMatch = colour.match "red" { - red = "It is in fact red!"; - blue = throw "It should not be blue!"; - green = throw "It should not be green!"; - }; - - # Test sum type definitions - creature = sum "creature" { - human = struct { - name = string; - age = option int; - }; - - pet = enum "pet" [ "dog" "lizard" "cat" ]; - }; - - testSum = creature { - human = { - name = "Brynhjulf"; - age = 42; - }; - }; - - testSumMatch = creature.match testSum { - human = v: "It's a human named ${v.name}"; - pet = v: throw "It's not supposed to be a pet!"; - }; - - # Test curried function definitions - func = defun [ string int string ] - (name: age: "${name} is ${toString age} years old"); - - testFunc = func "Brynhjulf" 42; - - # Test that all types are types. - testTypes = map type [ - any bool drv float int string - - (attrs int) - (either int string) - (enum [ "foo" "bar" ]) - (list string) - (option int) - (option (list string)) - (struct { a = int; b = option string; }) - (sum { a = int; b = option string; }) - ]; -} "All tests passed!\n" -- cgit 1.4.1