sanctuary-type-classes
The Fantasy Land Specification "specifies interoperability of common
algebraic structures" by defining a number of type classes. For each type
class, it states laws which every member of a type must obey in order for
the type to be a member of the type class. In order for the Maybe type to
be considered a Functor, for example, every Maybe a
value must have
a fantasy-land/map
method which obeys the identity and composition laws.
This project provides:
TypeClass
, a function for defining type classes;- one
TypeClass
value for each Fantasy Land type class; - lawful Fantasy Land methods for JavaScript's built-in types;
- one function for each Fantasy Land method; and
- several functions derived from these functions.
Type-class hierarchy
Setoid Semigroupoid Semigroup Foldable Functor Contravariant Filterable
(equals) (compose) (concat) (reduce) (map) (contramap) (filter)
| | | \ / | | | | \
| | | \ / | | | | \
| | | \ / | | | | \
| | | \ / | | | | \
| | | \ / | | | | \
Ord Category Monoid Traversable | | | | \
(lte) (id) (empty) (traverse) / | | \ \
| / | | \ \
| / / \ \ \
| Profunctor / \ Bifunctor \
| (promap) / \ (bimap) \
| / \ \
Group / \ \
(invert) Alt Apply Extend
(alt) (ap) (extend)
/ / \ \
/ / \ \
/ / \ \
/ / \ \
/ / \ \
Plus Applicative Chain Comonad
(zero) (of) (chain) (extract)
\ / \ / \
\ / \ / \
\ / \ / \
\ / \ / \
\ / \ / \
Alternative Monad ChainRec
(chainRec)
API
The arguments are:
- the name of the type class, prefixed by its npm package name;
- the documentation URL of the type class;
- an array of dependencies; and
- a predicate which accepts any JavaScript value and returns
true
if the value satisfies the requirements of the type class; false
otherwise.
Example:
const hasMethod = name => x => x != null && typeof x[name] == 'function';
const Foo = Z.TypeClass (
'my-package/Foo',
'http://example.com/my-package#Foo',
[],
hasMethod ('foo')
);
const Bar = Z.TypeClass (
'my-package/Bar',
'http://example.com/my-package#Bar',
[Foo],
hasMethod ('bar')
);
Types whose values have a foo
method are members of the Foo type class.
Members of the Foo type class whose values have a bar
method are also
members of the Bar type class.
Each TypeClass
value has a test
field: a function which accepts
any JavaScript value and returns true
if the value satisfies the
type class's predicate and the predicates of all the type class's
dependencies; false
otherwise.
TypeClass
values may be used with sanctuary-def
to define parametrically polymorphic functions which verify their
type-class constraints at run time.
TypeClass
value for Setoid.
> Z.Setoid.test (null)
true
> Z.Setoid.test (Useless)
false
> Z.Setoid.test ([1, 2, 3])
true
> Z.Setoid.test ([Useless])
false
TypeClass
value for Ord.
> Z.Ord.test (0)
true
> Z.Ord.test (Math.sqrt)
false
> Z.Ord.test ([1, 2, 3])
true
> Z.Ord.test ([Math.sqrt])
false
TypeClass
value for Semigroupoid.
> Z.Semigroupoid.test (Math.sqrt)
true
> Z.Semigroupoid.test (0)
false
TypeClass
value for Category.
> Z.Category.test (Math.sqrt)
true
> Z.Category.test (0)
false
TypeClass
value for Semigroup.
> Z.Semigroup.test ('')
true
> Z.Semigroup.test (0)
false
TypeClass
value for Monoid.
> Z.Monoid.test ('')
true
> Z.Monoid.test (0)
false
TypeClass
value for Group.
> Z.Group.test (Sum (0))
true
> Z.Group.test ('')
false
TypeClass
value for Filterable.
> Z.Filterable.test ({})
true
> Z.Filterable.test ('')
false
TypeClass
value for Functor.
> Z.Functor.test ([])
true
> Z.Functor.test ('')
false
TypeClass
value for Bifunctor.
> Z.Bifunctor.test (Pair ('foo') (64))
true
> Z.Bifunctor.test ([])
false
TypeClass
value for Profunctor.
> Z.Profunctor.test (Math.sqrt)
true
> Z.Profunctor.test ([])
false
TypeClass
value for Apply.
> Z.Apply.test ([])
true
> Z.Apply.test ('')
false
TypeClass
value for Applicative.
> Z.Applicative.test ([])
true
> Z.Applicative.test ({})
false
TypeClass
value for Chain.
> Z.Chain.test ([])
true
> Z.Chain.test ({})
false
TypeClass
value for ChainRec.
> Z.ChainRec.test ([])
true
> Z.ChainRec.test ({})
false
TypeClass
value for Monad.
> Z.Monad.test ([])
true
> Z.Monad.test ({})
false
TypeClass
value for Alt.
> Z.Alt.test ({})
true
> Z.Alt.test ('')
false
TypeClass
value for Plus.
> Z.Plus.test ({})
true
> Z.Plus.test ('')
false
TypeClass
value for Alternative.
> Z.Alternative.test ([])
true
> Z.Alternative.test ({})
false
TypeClass
value for Foldable.
> Z.Foldable.test ({})
true
> Z.Foldable.test ('')
false
TypeClass
value for Traversable.
> Z.Traversable.test ([])
true
> Z.Traversable.test ('')
false
TypeClass
value for Extend.
> Z.Extend.test ([])
true
> Z.Extend.test ({})
false
TypeClass
value for Comonad.
> Z.Comonad.test (Identity (0))
true
> Z.Comonad.test ([])
false
TypeClass
value for Contravariant.
> Z.Contravariant.test (Math.sqrt)
true
> Z.Contravariant.test ([])
false
Returns true
if its arguments are equal; false
otherwise.
Specifically:
-
Arguments with different type identities are unequal.
-
If the first argument has a fantasy-land/equals
method,
that method is invoked to determine whether the arguments are
equal (fantasy-land/equals
implementations are provided for the
following built-in types: Null, Undefined, Boolean, Number, Date,
RegExp, String, Array, Arguments, Error, Object, and Function).
-
Otherwise, the arguments are equal if their
entries are equal (according to this algorithm).
The algorithm supports circular data structures. Two arrays are equal
if they have the same index paths and for each path have equal values.
Two arrays which represent [1, [1, [1, [1, [1, ...]]]]]
, for example,
are equal even if their internal structures differ. Two objects are equal
if they have the same property paths and for each path have equal values.
> Z.equals (0, -0)
true
> Z.equals (NaN, NaN)
true
> Z.equals (Cons (1, Cons (2, Nil)), Cons (1, Cons (2, Nil)))
true
> Z.equals (Cons (1, Cons (2, Nil)), Cons (2, Cons (1, Nil)))
false
Returns true
if its arguments are of the same type and the first is
less than the second according to the type's fantasy-land/lte
method; false
otherwise.
This function is derived from lte
.
See also gt
and gte
.
> Z.lt (0, 0)
false
> Z.lt (0, 1)
true
> Z.lt (1, 0)
false
Returns true
if its arguments are of the same type and the first
is less than or equal to the second according to the type's
fantasy-land/lte
method; false
otherwise.
fantasy-land/lte
implementations are provided for the following
built-in types: Null, Undefined, Boolean, Number, Date, String, Array,
Arguments, and Object.
The algorithm supports circular data structures in the same manner as
equals
.
See also lt
, gt
, and gte
.
> Z.lte (0, 0)
true
> Z.lte (0, 1)
true
> Z.lte (1, 0)
false
Returns true
if its arguments are of the same type and the first is
greater than the second according to the type's fantasy-land/lte
method; false
otherwise.
This function is derived from lte
.
See also lt
and gte
.
> Z.gt (0, 0)
false
> Z.gt (0, 1)
false
> Z.gt (1, 0)
true
Returns true
if its arguments are of the same type and the first
is greater than or equal to the second according to the type's
fantasy-land/lte
method; false
otherwise.
This function is derived from lte
.
See also lt
and gt
.
> Z.gte (0, 0)
true
> Z.gte (0, 1)
false
> Z.gte (1, 0)
true
Returns the smaller of its two arguments.
This function is derived from lte
.
See also max
.
> Z.min (10, 2)
2
> Z.min (new Date ('1999-12-31'), new Date ('2000-01-01'))
new Date ('1999-12-31')
> Z.min ('10', '2')
'10'
Returns the larger of its two arguments.
This function is derived from lte
.
See also min
.
> Z.max (10, 2)
10
> Z.max (new Date ('1999-12-31'), new Date ('2000-01-01'))
new Date ('2000-01-01')
> Z.max ('10', '2')
'2'
Takes a lower bound, an upper bound, and a value of the same type.
Returns the value if it is within the bounds; the nearer bound otherwise.
This function is derived from min
and max
.
> Z.clamp (0, 100, 42)
42
> Z.clamp (0, 100, -1)
0
> Z.clamp ('A', 'Z', '~')
'Z'
Function wrapper for fantasy-land/compose
.
fantasy-land/compose
implementations are provided for the following
built-in types: Function.
> Z.compose (Math.sqrt, x => x + 1) (99)
10
Function wrapper for fantasy-land/id
.
fantasy-land/id
implementations are provided for the following
built-in types: Function.
> Z.id (Function) ('foo')
'foo'
Function wrapper for fantasy-land/concat
.
fantasy-land/concat
implementations are provided for the following
built-in types: String, Array, and Object.
> Z.concat ('abc', 'def')
'abcdef'
> Z.concat ([1, 2, 3], [4, 5, 6])
[1, 2, 3, 4, 5, 6]
> Z.concat ({x: 1, y: 2}, {y: 3, z: 4})
{x: 1, y: 3, z: 4}
> Z.concat (Cons ('foo', Cons ('bar', Cons ('baz', Nil))), Cons ('quux', Nil))
Cons ('foo', Cons ('bar', Cons ('baz', Cons ('quux', Nil))))
Function wrapper for fantasy-land/empty
.
fantasy-land/empty
implementations are provided for the following
built-in types: String, Array, and Object.
> Z.empty (String)
''
> Z.empty (Array)
[]
> Z.empty (Object)
{}
> Z.empty (List)
Nil
Function wrapper for fantasy-land/invert
.
> Z.invert (Sum (5))
Sum (-5)
Function wrapper for fantasy-land/filter
. Discards every element
which does not satisfy the predicate.
fantasy-land/filter
implementations are provided for the following
built-in types: Array and Object.
See also reject
.
> Z.filter (x => x % 2 == 1, [1, 2, 3])
[1, 3]
> Z.filter (x => x % 2 == 1, {x: 1, y: 2, z: 3})
{x: 1, z: 3}
> Z.filter (x => x % 2 == 1, Cons (1, Cons (2, Cons (3, Nil))))
Cons (1, Cons (3, Nil))
> Z.filter (x => x % 2 == 1, Nothing)
Nothing
> Z.filter (x => x % 2 == 1, Just (0))
Nothing
> Z.filter (x => x % 2 == 1, Just (1))
Just (1)
Discards every element which satisfies the predicate.
This function is derived from filter
.
> Z.reject (x => x % 2 == 1, [1, 2, 3])
[2]
> Z.reject (x => x % 2 == 1, {x: 1, y: 2, z: 3})
{y: 2}
> Z.reject (x => x % 2 == 1, Cons (1, Cons (2, Cons (3, Nil))))
Cons (2, Nil)
> Z.reject (x => x % 2 == 1, Nothing)
Nothing
> Z.reject (x => x % 2 == 1, Just (0))
Just (0)
> Z.reject (x => x % 2 == 1, Just (1))
Nothing
Function wrapper for fantasy-land/map
.
fantasy-land/map
implementations are provided for the following
built-in types: Array, Object, and Function.
> Z.map (Math.sqrt, [1, 4, 9])
[1, 2, 3]
> Z.map (Math.sqrt, {x: 1, y: 4, z: 9})
{x: 1, y: 2, z: 3}
> Z.map (Math.sqrt, s => s.length) ('Sanctuary')
3
> Z.map (Math.sqrt, Pair ('foo') (64))
Pair ('foo') (8)
> Z.map (Math.sqrt, Nil)
Nil
> Z.map (Math.sqrt, Cons (1, Cons (4, Cons (9, Nil))))
Cons (1, Cons (2, Cons (3, Nil)))
Maps over the given functions, applying each to the given value.
This function is derived from map
.
> Z.flip (x => y => x + y, '!') ('foo')
'foo!'
> Z.flip ([Math.floor, Math.ceil], 1.5)
[1, 2]
> Z.flip ({floor: Math.floor, ceil: Math.ceil}, 1.5)
{floor: 1, ceil: 2}
> Z.flip (Cons (Math.floor, Cons (Math.ceil, Nil)), 1.5)
Cons (1, Cons (2, Nil))
Function wrapper for fantasy-land/bimap
.
> Z.bimap (s => s.toUpperCase (), Math.sqrt, Pair ('foo') (64))
Pair ('FOO') (8)
Maps the given function over the left side of a Bifunctor.
> Z.mapLeft (Math.sqrt, Pair (64) (9))
Pair (8) (9)
Function wrapper for fantasy-land/promap
.
fantasy-land/promap
implementations are provided for the following
built-in types: Function.
> Z.promap (Math.abs, x => x + 1, Math.sqrt) (-100)
11
Function wrapper for fantasy-land/ap
.
fantasy-land/ap
implementations are provided for the following
built-in types: Array, Object, and Function.
> Z.ap ([Math.sqrt, x => x * x], [1, 4, 9, 16, 25])
[1, 2, 3, 4, 5, 1, 16, 81, 256, 625]
> Z.ap ({a: Math.sqrt, b: x => x * x}, {a: 16, b: 10, c: 1})
{a: 4, b: 100}
> Z.ap (s => n => s.slice (0, n), s => Math.ceil (s.length / 2)) ('Haskell')
'Hask'
> Z.ap (Identity (Math.sqrt), Identity (64))
Identity (8)
> Z.ap (Cons (Math.sqrt, Cons (x => x * x, Nil)), Cons (16, Cons (100, Nil)))
Cons (4, Cons (10, Cons (256, Cons (10000, Nil))))
Lifts a -> b -> c
to Apply f => f a -> f b -> f c
and returns the
result of applying this to the given arguments.
This function is derived from map
and ap
.
See also lift3
.
> Z.lift2 (x => y => Math.pow (x, y), [10], [1, 2, 3])
[10, 100, 1000]
> Z.lift2 (x => y => Math.pow (x, y), Identity (10), Identity (3))
Identity (1000)
Lifts a -> b -> c -> d
to Apply f => f a -> f b -> f c -> f d
and
returns the result of applying this to the given arguments.
This function is derived from map
and ap
.
See also lift2
.
> Z.lift3 (x => y => z => x + z + y,
. ['<', '['],
. ['>', ']'],
. ['foo', 'bar', 'baz'])
[ '<foo>', '<bar>', '<baz>',
. '<foo]', '<bar]', '<baz]',
. '[foo>', '[bar>', '[baz>',
. '[foo]', '[bar]', '[baz]' ]
> Z.lift3 (x => y => z => x + z + y,
. Identity ('<'),
. Identity ('>'),
. Identity ('baz'))
Identity ('<baz>')
Combines two effectful actions, keeping only the result of the first.
Equivalent to Haskell's (<*)
function.
This function is derived from lift2
.
See also apSecond
.
> Z.apFirst ([1, 2], [3, 4])
[1, 1, 2, 2]
> Z.apFirst (Identity (1), Identity (2))
Identity (1)
Combines two effectful actions, keeping only the result of the second.
Equivalent to Haskell's (*>)
function.
This function is derived from lift2
.
See also apFirst
.
> Z.apSecond ([1, 2], [3, 4])
[3, 4, 3, 4]
> Z.apSecond (Identity (1), Identity (2))
Identity (2)
Function wrapper for fantasy-land/of
.
fantasy-land/of
implementations are provided for the following
built-in types: Array and Function.
> Z.of (Array, 42)
[42]
> Z.of (Function, 42) (null)
42
> Z.of (List, 42)
Cons (42, Nil)
Returns the result of appending the first argument to the second.
This function is derived from concat
and of
.
See also prepend
.
> Z.append (3, [1, 2])
[1, 2, 3]
> Z.append (3, Cons (1, Cons (2, Nil)))
Cons (1, Cons (2, Cons (3, Nil)))
Returns the result of prepending the first argument to the second.
This function is derived from concat
and of
.
See also append
.
> Z.prepend (1, [2, 3])
[1, 2, 3]
> Z.prepend (1, Cons (2, Cons (3, Nil)))
Cons (1, Cons (2, Cons (3, Nil)))
Function wrapper for fantasy-land/chain
.
fantasy-land/chain
implementations are provided for the following
built-in types: Array and Function.
> Z.chain (x => [x, x], [1, 2, 3])
[1, 1, 2, 2, 3, 3]
> Z.chain (x => x % 2 == 1 ? Z.of (List, x) : Nil,
. Cons (1, Cons (2, Cons (3, Nil))))
Cons (1, Cons (3, Nil))
> Z.chain (n => s => s.slice (0, n),
. s => Math.ceil (s.length / 2))
. ('Haskell')
'Hask'
Removes one level of nesting from a nested monadic structure.
This function is derived from chain
.
> Z.join ([[1], [2], [3]])
[1, 2, 3]
> Z.join ([[[1, 2, 3]]])
[[1, 2, 3]]
> Z.join (Identity (Identity (1)))
Identity (1)
Function wrapper for fantasy-land/chainRec
.
fantasy-land/chainRec
implementations are provided for the following
built-in types: Array.
> Z.chainRec (
. Array,
. (next, done, s) => s.length == 2 ? [s + '!', s + '?'].map (done)
. : [s + 'o', s + 'n'].map (next),
. ''
. )
['oo!', 'oo?', 'on!', 'on?', 'no!', 'no?', 'nn!', 'nn?']
Function wrapper for fantasy-land/alt
.
fantasy-land/alt
implementations are provided for the following
built-in types: Array and Object.
> Z.alt ([1, 2, 3], [4, 5, 6])
[1, 2, 3, 4, 5, 6]
> Z.alt (Nothing, Nothing)
Nothing
> Z.alt (Nothing, Just (1))
Just (1)
> Z.alt (Just (2), Just (3))
Just (2)
Function wrapper for fantasy-land/zero
.
fantasy-land/zero
implementations are provided for the following
built-in types: Array and Object.
> Z.zero (Array)
[]
> Z.zero (Object)
{}
> Z.zero (Maybe)
Nothing
Function wrapper for fantasy-land/reduce
.
fantasy-land/reduce
implementations are provided for the following
built-in types: Array and Object.
> Z.reduce ((xs, x) => [x].concat (xs), [], [1, 2, 3])
[3, 2, 1]
> Z.reduce (Z.concat, '', Cons ('foo', Cons ('bar', Cons ('baz', Nil))))
'foobarbaz'
> Z.reduce (Z.concat, '', {foo: 'x', bar: 'y', baz: 'z'})
'yzx'
Returns the number of elements of the given structure.
This function is derived from reduce
.
> Z.size ([])
0
> Z.size (['foo', 'bar', 'baz'])
3
> Z.size (Nil)
0
> Z.size (Cons ('foo', Cons ('bar', Cons ('baz', Nil))))
3
Returns true
if all the elements of the structure satisfy the
predicate; false
otherwise.
This function is derived from reduce
.
See also any
and none
.
> Z.all (Number.isInteger, [])
true
> Z.all (Number.isInteger, [1, 2, 3])
true
> Z.all (Number.isInteger, [0, 0.25, 0.5, 0.75, 1])
false
Returns true
if any element of the structure satisfies the predicate;
false
otherwise.
This function is derived from reduce
.
See also all
and none
.
> Z.any (Number.isInteger, [])
false
> Z.any (Number.isInteger, [1, 2, 3])
true
> Z.any (Number.isInteger, [0, 0.25, 0.5, 0.75, 1])
true
Returns true
if none of the elements of the structure satisfies the
predicate; false
otherwise.
This function is derived from any
. Z.none (pred, foldable)
is
equivalent to !(Z.any (pred, foldable))
.
See also all
.
> Z.none (Number.isInteger, [])
true
> Z.none (Number.isInteger, [0, 0.25, 0.5, 0.75, 1])
false
Takes a value and a structure and returns true
if the
value is an element of the structure; false
otherwise.
This function is derived from equals
and
reduce
.
> Z.elem ('c', ['a', 'b', 'c'])
true
> Z.elem ('x', ['a', 'b', 'c'])
false
> Z.elem (3, {x: 1, y: 2, z: 3})
true
> Z.elem (8, {x: 1, y: 2, z: 3})
false
> Z.elem (0, Just (0))
true
> Z.elem (0, Just (1))
false
> Z.elem (0, Nothing)
false
Concatenates the elements of the given structure, separating each pair
of adjacent elements with the given separator.
This function is derived from concat
, empty
,
and reduce
.
> Z.intercalate (', ', [])
''
> Z.intercalate (', ', ['foo', 'bar', 'baz'])
'foo, bar, baz'
> Z.intercalate (', ', Nil)
''
> Z.intercalate (', ', Cons ('foo', Cons ('bar', Cons ('baz', Nil))))
'foo, bar, baz'
> Z.intercalate ([0, 0, 0], [])
[]
> Z.intercalate ([0, 0, 0], [[1], [2, 3], [4, 5, 6], [7, 8], [9]])
[1, 0, 0, 0, 2, 3, 0, 0, 0, 4, 5, 6, 0, 0, 0, 7, 8, 0, 0, 0, 9]
Deconstructs a foldable by mapping every element to a monoid and
concatenating the results.
This function is derived from concat
, empty
,
and reduce
.
> Z.foldMap (String, f => f.name, [Math.sin, Math.cos, Math.tan])
'sincostan'
Reverses the elements of the given structure.
This function is derived from concat
, empty
,
of
, and reduce
.
> Z.reverse ([1, 2, 3])
[3, 2, 1]
> Z.reverse (Cons (1, Cons (2, Cons (3, Nil))))
Cons (3, Cons (2, Cons (1, Nil)))
Performs a stable sort of the elements of the given structure,
using lte
for comparisons.
This function is derived from lte
, concat
,
empty
, of
, and reduce
.
See also sortBy
.
> Z.sort (['foo', 'bar', 'baz'])
['bar', 'baz', 'foo']
> Z.sort ([Just (2), Nothing, Just (1)])
[Nothing, Just (1), Just (2)]
> Z.sort (Cons ('foo', Cons ('bar', Cons ('baz', Nil))))
Cons ('bar', Cons ('baz', Cons ('foo', Nil)))
Performs a stable sort of the elements of the given structure,
using lte
to compare the values produced by applying the
given function to each element of the structure.
This function is derived from lte
, concat
,
empty
, of
, and reduce
.
See also sort
.
> Z.sortBy (s => s.length, ['red', 'green', 'blue'])
['red', 'blue', 'green']
> Z.sortBy (s => s.length, ['black', 'white'])
['black', 'white']
> Z.sortBy (s => s.length, ['white', 'black'])
['white', 'black']
> Z.sortBy (s => s.length, Cons ('red', Cons ('green', Cons ('blue', Nil))))
Cons ('red', Cons ('blue', Cons ('green', Nil)))
Function wrapper for fantasy-land/traverse
.
fantasy-land/traverse
implementations are provided for the following
built-in types: Array and Object.
See also sequence
.
> Z.traverse (Array, x => x, [[1, 2, 3], [4, 5]])
[[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
> Z.traverse (Identity, x => Identity (x + 1), [1, 2, 3])
Identity ([2, 3, 4])
Inverts the given t (f a)
to produce an f (t a)
.
This function is derived from traverse
.
> Z.sequence (Array, Identity ([1, 2, 3]))
[Identity (1), Identity (2), Identity (3)]
> Z.sequence (Identity, [Identity (1), Identity (2), Identity (3)])
Identity ([1, 2, 3])
Function wrapper for fantasy-land/extend
.
fantasy-land/extend
implementations are provided for the following
built-in types: Array and Function.
> Z.extend (ss => ss.join (''), ['x', 'y', 'z'])
['xyz', 'yz', 'z']
> Z.extend (f => f ([3, 4]), Z.reverse) ([1, 2])
[4, 3, 2, 1]
Adds one level of nesting to a comonadic structure.
This function is derived from extend
.
> Z.duplicate (Identity (1))
Identity (Identity (1))
> Z.duplicate ([1])
[[1]]
> Z.duplicate ([1, 2, 3])
[[1, 2, 3], [2, 3], [3]]
> Z.duplicate (Z.reverse) ([1, 2]) ([3, 4])
[4, 3, 2, 1]
Function wrapper for fantasy-land/extract
.
> Z.extract (Identity (42))
42
Function wrapper for fantasy-land/contramap
.
fantasy-land/contramap
implementations are provided for the following
built-in types: Function.
> Z.contramap (s => s.length, Math.sqrt) ('Sanctuary')
3