Tricu 2.0.0
Sorry for squashing all of this but 🤷
This commit is contained in:
190
demos/viewContracts.tri
Normal file
190
demos/viewContracts.tri
Normal file
@@ -0,0 +1,190 @@
|
||||
!import "prelude" !Local
|
||||
!import "view" !Local
|
||||
|
||||
-- ============================================================================
|
||||
-- View Contracts in tricu
|
||||
-- ============================================================================
|
||||
--
|
||||
-- Verify this guide passes checking with:
|
||||
--
|
||||
-- tricu check demos/viewContracts.tri
|
||||
--
|
||||
-- Expected output:
|
||||
--
|
||||
-- ok
|
||||
--
|
||||
-- This file uses tricu syntax sugar. The lower-level portable View Tree
|
||||
-- form is shown in demos/viewContracts/complete.tri.
|
||||
|
||||
-- ============================================================================
|
||||
-- 1. What's the problem?
|
||||
-- ============================================================================
|
||||
--
|
||||
-- Programs grow by connecting definitions. A common mistake is connecting a
|
||||
-- value with one shape to code that expects another shape:
|
||||
--
|
||||
-- a function expects Bool, but receives String
|
||||
-- a function returns String, but its caller expects Bool
|
||||
-- a list is expected to contain bytes, but contains strings
|
||||
--
|
||||
-- In a large program, those mistakes are often far away from where the bad value
|
||||
-- was first introduced. View Contracts give tricu a portable way to check those
|
||||
-- boundaries.
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. Views: useful built-in shapes
|
||||
-- ============================================================================
|
||||
--
|
||||
-- A View is a description of the shape we expect at a boundary. tricu includes
|
||||
-- built-in Views for common shapes such as:
|
||||
--
|
||||
-- Bool
|
||||
-- String
|
||||
-- Byte
|
||||
-- Unit
|
||||
-- List View
|
||||
-- Maybe View
|
||||
-- Pair View1 View2
|
||||
-- Fn [View1] View2
|
||||
--
|
||||
-- tricu has unconventional but intuitive sugar for annotations:
|
||||
--
|
||||
-- name =@View value
|
||||
-- function argument@View =@ResultView body
|
||||
--
|
||||
-- These examples are ordinary checked source definitions.
|
||||
|
||||
message =@String "hello"
|
||||
|
||||
names =@(List String) [("Ada") ("Grace")]
|
||||
|
||||
chooseFirst left@String right@String =@String left
|
||||
|
||||
stringIdentity =@(Fn [String] String) (x : x)
|
||||
|
||||
-- Uncommenting the below definition demonstrates a plain View mismatch:
|
||||
--
|
||||
-- bad =@Bool "not a Bool"
|
||||
--
|
||||
-- `tricu check` reports that the value is known as String where Bool was
|
||||
-- required.
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. Why don't you just have Types?
|
||||
-- ============================================================================
|
||||
--
|
||||
-- tricu is built on Tree Calculus. A defining feature of Tree Calculus is
|
||||
-- intensionality: programs can inspect and construct program-shaped trees directly.
|
||||
-- That intensional power is useful, but it makes ordinary sound static typing a
|
||||
-- hard fit. A value can be both data and executable structure, and code can make
|
||||
-- decisions based on tree shape in ways a conventional type checker may not be
|
||||
-- able to predict soundly. This is an area of active research, not a settled
|
||||
-- claim that Tree Calculus languages cannot ever have useful typed variants.
|
||||
--
|
||||
-- View Contracts are not advertised as "the type system for tricu". They are
|
||||
-- a practical contract layer: portable metadata plus checker/runtime boundaries
|
||||
-- that catch many real mistakes while leaving the underlying language intact.
|
||||
|
||||
-- For more information about sound typing for Tree Calculus:
|
||||
-- https://github.com/barry-jay-personal/typed_tree_calculus
|
||||
|
||||
-- ============================================================================
|
||||
-- 4. What are the Contracts about, then?
|
||||
-- ============================================================================
|
||||
--
|
||||
-- `List String` tells us that every element is a String. It does not tell us the
|
||||
-- list has at least one element.
|
||||
--
|
||||
-- That matters for functions like `head`. Calling `head` on an empty list is a
|
||||
-- bug. We want to express the stronger requirement:
|
||||
--
|
||||
-- this is a List String, and it is non-empty
|
||||
--
|
||||
-- That is what a guarded View is for.
|
||||
|
||||
-- A guard is ordinary tricu code. It receives the runtime value and returns:
|
||||
--
|
||||
-- guardOk value -- accept the value
|
||||
-- guardFail -- reject the boundary
|
||||
--
|
||||
-- The guard does not write diagnostics. The checked runner reports where the
|
||||
-- failing boundary came from.
|
||||
|
||||
requireNonEmpty = (xs :
|
||||
lazyBool
|
||||
(_ : guardFail)
|
||||
(_ : guardOk xs)
|
||||
(emptyList? xs))
|
||||
|
||||
-- A user-defined View can be parameterized just like an ordinary function.
|
||||
--
|
||||
-- NonEmptyList String
|
||||
--
|
||||
-- means "a List String guarded by requireNonEmpty".
|
||||
|
||||
NonEmptyList elem = viewGuarded (viewList elem) requireNonEmpty
|
||||
|
||||
-- ============================================================================
|
||||
-- 5. Using a custom View in normal annotations
|
||||
-- ============================================================================
|
||||
--
|
||||
-- This value satisfies the custom contract.
|
||||
|
||||
contributors =@(NonEmptyList String) [("Ada") ("Grace")]
|
||||
|
||||
-- This function requires NonEmptyList String before its body can run. In a
|
||||
-- library, this is the kind of contract you would put on an operation like
|
||||
-- `head`: callers must prove the list is non-empty first.
|
||||
|
||||
acceptNames xs@(NonEmptyList String) =@String "accepted non-empty names"
|
||||
|
||||
primaryContributor =@String acceptNames contributors
|
||||
|
||||
-- Uncommenting this definition demonstrates a guarded View failure:
|
||||
--
|
||||
-- nobody =@(NonEmptyList String) []
|
||||
--
|
||||
-- The structure is fine (`[]` is a List String), but the runtime guard rejects
|
||||
-- it because the list is empty.
|
||||
|
||||
-- ============================================================================
|
||||
-- 6. Contracts protect callers too
|
||||
-- ============================================================================
|
||||
--
|
||||
-- Contracts can describe function results as well as arguments. If a function
|
||||
-- promises to return `NonEmptyList String`, checked execution guards that result
|
||||
-- before callers depend on it.
|
||||
|
||||
mkContributors name@String =@(NonEmptyList String) [(name)]
|
||||
|
||||
fromSingleName =@String acceptNames (mkContributors "Evelyn")
|
||||
|
||||
-- Uncommenting this version would fail because the result contract is too
|
||||
-- strong for the implementation:
|
||||
--
|
||||
-- badContributors name@String =@(NonEmptyList String) []
|
||||
|
||||
-- ============================================================================
|
||||
-- 7. Writing your own Views and Contracts
|
||||
-- ============================================================================
|
||||
--
|
||||
-- The pattern is:
|
||||
--
|
||||
-- 1. Start with the closest structural View.
|
||||
-- 2. Write a guard for the runtime fact the structure cannot express.
|
||||
-- 3. Package them with viewGuarded.
|
||||
-- 4. Use the new View in normal annotations.
|
||||
--
|
||||
-- Examples of useful guarded Views:
|
||||
--
|
||||
-- NonEmptyList String
|
||||
-- SortedList Byte
|
||||
-- FixedLengthBytes 32
|
||||
-- ValidUserId
|
||||
-- NonEmptyString
|
||||
--
|
||||
-- Guards are intentionally runtime checks. Use plain Views for ordinary shape
|
||||
-- checking, and guarded Views when a boundary really must enforce a stronger
|
||||
-- invariant.
|
||||
|
||||
main =@String primaryContributor
|
||||
Reference in New Issue
Block a user