Fun fact: The type annotations for the functions in this package are pretty horrifying.
Hot tip: Ignore them! There's no need to put any of these functions, or the tools they produce, in your
application's Msg
or Model
type. So you don't need to know or care about what their type signatures are. Just read
the docs and look at the examples. It'll be fine.*
* Unless you make any kind of minor typo in your code, in which case the Elm compiler may respond with a Lovecraftian error message 😱.
define : b -> c -> Builder { after : {}, afters : {}, applyDelta : a49 -> a49, array : a48 -> a48, arrayMaker : a47 -> a47, before : a46 -> a46, befores : a45 -> a45, bool : a44 -> a44, char : a43 -> a43, constructMultiTool : a42 -> a42, constructTweak : a41 -> a41, custom : a40 -> a40, customEnder : a39 -> a39, customMaker : a38 -> a38, destructorFieldGetter : a37 -> a37, dict : a36 -> a36, dictMaker : a35 -> a35, endCustom : a34 -> a34, endRecord : a33 -> a33, field : a32 -> a32, fieldMaker : a31 -> a31, float : a30 -> a30, int : a29 -> a29, list : a28 -> a28, listMaker : a27 -> a27, maybe : a26 -> a26, maybeMaker : a25 -> a25, record : a24 -> a24, recordEnder : a23 -> a23, recordMaker : a22 -> a22, result : a21 -> a21, resultMaker : a20 -> a20, set : a19 -> a19, setMaker : a18 -> a18, string : a17 -> a17, tag0 : a16 -> a16, tag0Maker : a15 -> a15, tag1 : a14 -> a14, tag1Maker : a13 -> a13, tag2 : a12 -> a12, tag2Maker : a11 -> a11, tag3 : a10 -> a10, tag3Maker : a9 -> a9, tag4 : a8 -> a8, tag4Maker : a7 -> a7, tag5 : a6 -> a6, tag5Maker : a5 -> a5, toolConstructor : b, triple : a4 -> a4, tripleMaker : a3 -> a3, tuple : a2 -> a2, tupleMaker : a1 -> a1, tweakConstructor : c, tweakerMaker : a -> a }
define
Begin creating a new MultiTool.
This function takes two identical arguments. Both arguments should be a function that constructs a record with a field for each of the tools you intend to include in your MultiTool.
For example, if you want to combine a ToString
tool and a ToComparable
tool, you could do something like this:
import ToComparable
import ToString
type alias Tools toString toComparable =
{ toString : toString
, toComparable : toComparable
}
tools =
MultiTool.define Tools Tools
|> MultiTool.add
.toString
ToString.interface
|> MultiTool.add
.toComparable
ToComparable.interface
|> MultiTool.end
Note: The order of the type parameters in the Tools
type alias is important. It needs to match the order in which
you add the tool interfaces with MultiTool.add
.
In this example, we see that toString
comes first and toComparable
comes second in both the Tools
type alias and
the interfaces added to tools
.
A data structure produced by define
, used to create MultiTools
add : a95 -> { r | array : a100, bool : a98, char : a97, custom : a96, dict : a94, endCustom : a93, endRecord : a92, field : a91, float : a90, int : a89, list : a88, maybe : a87, record : a86, result : a85, set : a84, string : a83, tag0 : a82, tag1 : a81, tag2 : a80, tag3 : a79, tag4 : a78, tag5 : a77, triple : a76, tuple : a75 } -> Builder { u1 | after : s, afters : t, applyDelta : a73 -> b20 -> a72 -> c47, array : ( a100, a71 ) -> c46, arrayMaker : a70 -> b19 -> a69 -> c45, before : ( Maybe a99, a68 ) -> c44, befores : ( ( Maybe a99, a68 ) -> c44, a67 ) -> c43, bool : ( a98, a66 ) -> c42, char : ( a97, a65 ) -> c41, constructMultiTool : a64 -> a63 -> b18 -> c40, constructTweak : a62 -> u -> v -> w, custom : ( a96, a61 ) -> c39, customEnder : a60 -> b17 -> a59 -> c38, customMaker : a58 -> b16 -> a57 -> c37 -> d16, destructorFieldGetter : ( a95, a56 ) -> c36, dict : ( a94, a55 ) -> c35, dictMaker : a54 -> b15 -> a53 -> c34 -> d15, endCustom : ( a93, a52 ) -> c33, endRecord : ( a92, a51 ) -> c32, field : ( a91, a50 ) -> c31, fieldMaker : a49 -> b14 -> c30 -> a48 -> d14 -> e13 -> f9, float : ( a90, a47 ) -> c29, int : ( a89, a46 ) -> c28, list : ( a88, a45 ) -> c27, listMaker : a44 -> b13 -> a43 -> c26, maybe : ( a87, a42 ) -> c25, maybeMaker : a41 -> b12 -> a40 -> c24, record : ( a86, a39 ) -> c23, recordEnder : a38 -> b11 -> a37 -> c22, recordMaker : a36 -> b10 -> a35 -> c21, result : ( a85, a34 ) -> c20, resultMaker : a33 -> b9 -> a32 -> c19 -> d9, set : ( a84, a31 ) -> c18, setMaker : a30 -> b8 -> a29 -> c17, string : ( a83, a28 ) -> c16, tag0 : ( a82, a27 ) -> c15, tag0Maker : a26 -> b7 -> c14 -> a25 -> d7 -> e7, tag1 : ( a81, a24 ) -> c13, tag1Maker : a23 -> b6 -> c12 -> a22 -> d6 -> e6 -> f6, tag2 : ( a80, a21 ) -> c11, tag2Maker : a20 -> b5 -> c10 -> a19 -> d5 -> e5 -> f5 -> g5, tag3 : ( a79, a18 ) -> c9, tag3Maker : a17 -> b4 -> c8 -> a16 -> d4 -> e4 -> f4 -> g4 -> h3, tag4 : ( a78, a15 ) -> c7, tag4Maker : a14 -> b3 -> c6 -> a13 -> d3 -> e3 -> f3 -> g3 -> h2 -> i2, tag5 : ( a77, a12 ) -> c5, tag5Maker : a11 -> b2 -> c4 -> a10 -> d2 -> e2 -> f2 -> g2 -> h1 -> i1 -> j, toolConstructor : x, triple : ( a76, a9 ) -> c3, tripleMaker : a8 -> b1 -> a7 -> c2 -> d1 -> e1, tuple : ( a75, a6 ) -> c1, tupleMaker : a5 -> b -> a4 -> c -> d, tweakConstructor : y, tweakerMaker : a3 -> (({} -> {} -> {}) -> q1 -> a1 -> a) -> r1 -> s1 -> t1 } -> Builder { after : ( Maybe a74, s ), afters : ( s, t ), applyDelta : a73 -> ( Maybe (d20 -> d20), b20 ) -> ( d20, a72 ) -> ( d20, c47 ), array : a71 -> c46, arrayMaker : a70 -> ( d19, b19 ) -> ( d19 -> e17, a69 ) -> ( e17, c45 ), before : a68 -> c44, befores : a67 -> c43, bool : a66 -> c42, char : a65 -> c41, constructMultiTool : a64 -> (d18 -> a63) -> ( d18, b18 ) -> c40, constructTweak : a62 -> (v1 -> u) -> ( v1, v ) -> w, custom : a61 -> c39, customEnder : a60 -> ( d17, b17 ) -> ( d17 -> e16, a59 ) -> ( e16, c38 ), customMaker : a58 -> b16 -> ( b16 -> e15, a57 ) -> ( e15 -> f11, c37 ) -> ( f11, d16 ), destructorFieldGetter : a56 -> c36, dict : a55 -> c35, dictMaker : a54 -> ( e14, b15 ) -> ( f10, a53 ) -> ( e14 -> f10 -> g10, c34 ) -> ( g10, d15 ), endCustom : a52 -> c33, endRecord : a51 -> c32, field : a50 -> c31, fieldMaker : a49 -> b14 -> c30 -> ( g9, a48 ) -> ( h6, d14 ) -> ( b14 -> c30 -> g9 -> h6 -> i6, e13 ) -> ( i6, f9 ), float : a47 -> c29, int : a46 -> c28, list : a45 -> c27, listMaker : a44 -> ( d13, b13 ) -> ( d13 -> e12, a43 ) -> ( e12, c26 ), maybe : a42 -> c25, maybeMaker : a41 -> ( d12, b12 ) -> ( d12 -> e11, a40 ) -> ( e11, c24 ), record : a39 -> c23, recordEnder : a38 -> ( d11, b11 ) -> ( d11 -> e10, a37 ) -> ( e10, c22 ), recordMaker : a36 -> b10 -> ( b10 -> d10, a35 ) -> ( d10, c21 ), result : a34 -> c20, resultMaker : a33 -> ( e9, b9 ) -> ( f8, a32 ) -> ( e9 -> f8 -> g8, c19 ) -> ( g8, d9 ), set : a31 -> c18, setMaker : a30 -> ( d8, b8 ) -> ( d8 -> e8, a29 ) -> ( e8, c17 ), string : a28 -> c16, tag0 : a27 -> c15, tag0Maker : a26 -> b7 -> c14 -> ( f7, a25 ) -> ( b7 -> c14 -> f7 -> g7, d7 ) -> ( g7, e7 ), tag1 : a24 -> c13, tag1Maker : a23 -> b6 -> c12 -> ( g6, a22 ) -> ( h5, d6 ) -> ( b6 -> c12 -> g6 -> h5 -> i5, e6 ) -> ( i5, f6 ), tag2 : a21 -> c11, tag2Maker : a20 -> b5 -> c10 -> ( h4, a19 ) -> ( i4, d5 ) -> ( j3, e5 ) -> ( b5 -> c10 -> h4 -> i4 -> j3 -> k3, f5 ) -> ( k3, g5 ), tag3 : a18 -> c9, tag3Maker : a17 -> b4 -> c8 -> ( i3, a16 ) -> ( j2, d4 ) -> ( k2, e4 ) -> ( l2, f4 ) -> ( b4 -> c8 -> i3 -> j2 -> k2 -> l2 -> m2, g4 ) -> ( m2, h3 ), tag4 : a15 -> c7, tag4Maker : a14 -> b3 -> c6 -> ( j1, a13 ) -> ( k1, d3 ) -> ( l1, e3 ) -> ( m1, f3 ) -> ( n1, g3 ) -> ( b3 -> c6 -> j1 -> k1 -> l1 -> m1 -> n1 -> o1, h2 ) -> ( o1, i2 ), tag5 : a12 -> c5, tag5Maker : a11 -> b2 -> c4 -> ( k, a10 ) -> ( l, d2 ) -> ( m, e2 ) -> ( n, f2 ) -> ( o, g2 ) -> ( p, h1 ) -> ( b2 -> c4 -> k -> l -> m -> n -> o -> p -> q, i1 ) -> ( q, j ), toolConstructor : x, triple : a9 -> c3, tripleMaker : a8 -> ( f1, b1 ) -> ( g1, a7 ) -> ( h, c2 ) -> ( f1 -> g1 -> h -> i, d1 ) -> ( i, e1 ), tuple : a6 -> c1, tupleMaker : a5 -> ( e, b ) -> ( f, a4 ) -> ( e -> f -> g, c ) -> ( g, d ), tweakConstructor : y, tweakerMaker : a3 -> (({} -> {} -> {}) -> q1 -> a1 -> a) -> ( ( Maybe a2, w1 ) -> q1, r1 ) -> ( w1, s1 ) -> ( a2 -> ToolSpec a1 -> ToolSpec a, t1 ) }
add
Add a tool to your MultiTool.
This function takes three arguments. The first is a field accessor, which should match one of the fields of the record
constructor you passed to MultiTool.define
. For example: .codec
.
The second is a tool interface - a record whose fields contain all the functions we will need to make the tool work.
Here's an example of an interface for the awesome miniBill/elm-codec
package:
import Codec
codecInterface =
{ string = Codec.string
, int = Codec.int
, bool = Codec.bool
, float = Codec.float
, char = Codec.char
, list = Codec.list
, maybe = Codec.maybe
, array = Codec.array
, dict =
\k v ->
Codec.tuple k v
|> Codec.list
|> Codec.map Dict.fromList Dict.toList
, set = Codec.set
, tuple = Codec.tuple
, triple = Codec.triple
, result = Codec.result
, record = Codec.object
, field = Codec.field
, endRecord = Codec.buildObject
, custom = Codec.custom
, tag0 = Codec.variant0
, tag1 = Codec.variant1
, tag2 = Codec.variant2
, tag3 = Codec.variant3
, tag4 = Codec.variant4
, tag5 = Codec.variant5
, endCustom = Codec.buildCustom
}
(You'll notice that this interface type mostly looks straightforward, except for the dict
field. The problem here
is that we need a Dict
implementation that can take any comparable
as a key, but elm-codec
's built-in dict
function only accepts String
s as keys. Fortunately, we can build the function we need using elm-codec
's lower-level
primitives.)
The third and final argument is the MultiTool.Builder
that you've already created using define
. The API here is
designed for piping with the |>
operator, so you'll usually want to apply this final argument like this:
tools =
MultiTool.define Tools Tools
|> MultiTool.add .codec codecInterface
end : Builder { v1 | afters : t, applyDelta : u, array : {} -> d11, arrayMaker : ({} -> {} -> {}) -> c6 -> d11 -> a14, befores : {} -> v, bool : {} -> a13, char : {} -> a12, constructMultiTool : (a24 -> {} -> a24) -> c7 -> d4 -> e6, constructTweak : (w -> {} -> w) -> y -> z -> r1, custom : {} -> f7, customEnder : ({} -> {} -> {}) -> c5 -> d10 -> a10, customMaker : (a23 -> {} -> {} -> {}) -> d3 -> e7 -> f7 -> g5, destructorFieldGetter : {} -> e7, dict : {} -> f6, dictMaker : ({} -> {} -> {} -> {}) -> d2 -> e5 -> f6 -> a11, endCustom : {} -> d10, endRecord : {} -> d7, field : {} -> j5, fieldMaker : (a22 -> b6 -> {} -> {} -> {} -> {}) -> f3 -> g4 -> h3 -> i5 -> j5 -> k5, float : {} -> a8, int : {} -> a7, list : {} -> d9, listMaker : ({} -> {} -> {}) -> c3 -> d9 -> a6, maybe : {} -> d8, maybeMaker : ({} -> {} -> {}) -> c2 -> d8 -> a5, record : {} -> d6, recordEnder : ({} -> {} -> {}) -> c4 -> d7 -> a9, recordMaker : (a21 -> {} -> {}) -> c1 -> d6 -> e4, result : {} -> f5, resultMaker : ({} -> {} -> {} -> {}) -> d1 -> e3 -> f5 -> a4, set : {} -> d5, setMaker : ({} -> {} -> {}) -> c -> d5 -> a3, string : {} -> a2, tag0 : {} -> h5, tag0Maker : (a20 -> b5 -> {} -> {} -> {}) -> e2 -> f2 -> g3 -> h5 -> i4, tag1 : {} -> j4, tag1Maker : (a19 -> b4 -> {} -> {} -> {} -> {}) -> f1 -> g2 -> h2 -> i3 -> j4 -> k4, tag2 : {} -> l3, tag2Maker : (a18 -> b3 -> {} -> {} -> {} -> {} -> {}) -> g1 -> h1 -> i2 -> j3 -> k3 -> l3 -> m3, tag3 : {} -> n2, tag3Maker : (a17 -> b2 -> {} -> {} -> {} -> {} -> {} -> {}) -> h -> i1 -> j2 -> k2 -> l2 -> m2 -> n2 -> o2, tag4 : {} -> p1, tag4Maker : (a16 -> b1 -> {} -> {} -> {} -> {} -> {} -> {} -> {}) -> i -> j1 -> k1 -> l1 -> m1 -> n1 -> o1 -> p1 -> q1, tag5 : {} -> r, tag5Maker : (a15 -> b -> {} -> {} -> {} -> {} -> {} -> {} -> {} -> {}) -> j -> k -> l -> m -> n -> o -> p -> q -> r -> s, toolConstructor : c7, triple : {} -> h4, tripleMaker : ({} -> {} -> {} -> {} -> {}) -> e1 -> f -> g -> h4 -> a1, tuple : {} -> f4, tupleMaker : ({} -> {} -> {} -> {}) -> d -> e -> f4 -> a, tweakConstructor : y, tweakerMaker : (s1 -> {} -> {} -> {}) -> u -> v -> t -> z } -> { array : ToolSpec c6 -> ToolSpec a14, bool : ToolSpec a13, build : ToolSpec d4 -> e6, char : ToolSpec a12, custom : d3 -> g5, dict : ToolSpec d2 -> ToolSpec e5 -> ToolSpec a11, endCustom : c5 -> ToolSpec a10, endRecord : c4 -> ToolSpec a9, field : f3 -> g4 -> ToolSpec h3 -> i5 -> k5, float : ToolSpec a8, int : ToolSpec a7, list : ToolSpec c3 -> ToolSpec a6, maybe : ToolSpec c2 -> ToolSpec a5, record : c1 -> e4, result : ToolSpec d1 -> ToolSpec e3 -> ToolSpec a4, set : ToolSpec c -> ToolSpec a3, string : ToolSpec a2, tag0 : e2 -> f2 -> g3 -> i4, tag1 : f1 -> g2 -> ToolSpec h2 -> i3 -> k4, tag2 : g1 -> h1 -> ToolSpec i2 -> ToolSpec j3 -> k3 -> m3, tag3 : h -> i1 -> ToolSpec j2 -> ToolSpec k2 -> ToolSpec l2 -> m2 -> o2, tag4 : i -> j1 -> ToolSpec k1 -> ToolSpec l1 -> ToolSpec m1 -> ToolSpec n1 -> o1 -> q1, tag5 : j -> k -> ToolSpec l -> ToolSpec m -> ToolSpec n -> ToolSpec o -> ToolSpec p -> q -> s, triple : ToolSpec e1 -> ToolSpec f -> ToolSpec g -> ToolSpec a1, tuple : ToolSpec d -> ToolSpec e -> ToolSpec a, tweak : r1 }
end
Complete the definition of a MultiTool.
tools =
MultiTool.define Tools Tools
|> MultiTool.add
.toString
ToString.interface
|> MultiTool.add
.toComparable
ToComparable.interface
|> MultiTool.end
This function converts a MultiTool.Builder
into a record that contains all the functions you'll need to create tool
specifications (ToolSpec
s) for the types in your application. If you define a MultiTool like tools
in the example
above, it'll give you access to the following ToolSpecs
and helper functions:
ToolSpec
s for primitive typesWe've got tools.bool
, tools.string
, tools.int
, tools.float
, and tools.char
.
For example:
boolTool =
tools.build tools.bool
trueFact =
boolTool.toString True == "True"
ToolSpec
s for common combinatorsWe've got tools.maybe
, tools.result
, tools.list
, tools.array
, tools.dict
, tools.set
, tools.tuple
, and
tools.triple
.
For example:
listBoolSpec =
tools.list tools.bool
tupleIntStringSpec =
tools.tuple tools.int tools.string
ToolSpec
s for combinators for custom typesTo define ToolSpec
s for your own custom types, use tools.custom
, tools.tag0
, tools.tag1
, tools.tag2
,
tools.tag3
, tools.tag4
, tools.tag5
, and tools.endCustom
.
For example, if we really hated Elm's Maybe
type and wanted it to be called Option
instead:
type Option value
= Some value
| None
optionSpec valueSpec =
let
match some none tag =
case tag of
Some value ->
some value
None ->
none
in
tools.custom
{ toString = match, toComparable = match }
|> tools.tag1 "Some" Some valueSpec
|> tools.tag0 "None" None
|> tools.endCustom
Note: You must define the match
function either in a let-in
block within your tool specification, or as a
top-level function in one of your modules. If you try and parameterise your tool specification to take a match
function as one of its arguments, it won't work - you'll get a compiler error.
The explanation for this is a bit beyond me, but I think it goes: "something something forall
, something something
let-polymorphism."
ToolSpec
s for combinators for recordsFor records (and indeed any kind of "product" type), check out tools.record
, tools.field
, and tools.endRecord
.
For example:
type alias User =
{ name : String, age : Int }
userToolSpec =
tools.record User
|> tools.field "name" .name tools.string
|> tools.field "age" .age tools.int
|> tools.endRecord
ToolSpec
into a usable tooltools.build
turns a ToolSpec
into a usable tool.
For example:
userTool =
tools.build userToolSpec
user =
{ name = "Ed", age = 41 }
trueFact =
userTool.toString user
== "{ name = \"Ed\", age = 41 }"
ToolSpec
tools.tweak
allows you to alter (or replace) the tools contained within a ToolSpec
.
For example, if you're using edkelly303/elm-any-type-forms
, you could customise the tools.string
and tools.int
ToolSpec
s like this:
import Control
myIntSpec =
tools.int
|> tools.tweak.control
(Control.failIf
(\int -> int < 0)
"Must be greater than or equal to 0"
)
myStringSpec =
tools.string
|> tools.tweak.control
(\_ ->
-- throw away whatever control we
-- had in tools.string, and use
-- this instead:
Control.string
|> Control.debounce 1000
)
A tool specification - the basic building block for creating tools for the types in your application.