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 😱.
A data structure produced by define, used to create MultiTools
addAdd 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
, customType = Codec.custom
, variant0 = Codec.variant0
, variant1 = Codec.variant1
, variant2 = Codec.variant2
, variant3 = Codec.variant3
, variant4 = Codec.variant4
, variant5 = Codec.variant5
, endCustomType = 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 Strings 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 codecInterfaceendComplete the definition of a MultiTool.
tools =
MultiTool.define Tools Tools
|> MultiTool.add
.toString
ToString.interface
|> MultiTool.add
.toComparable
ToComparable.interface
|> MultiTool.endThis function converts a MultiTool.Builder into a record that contains all the
functions you'll need to create tool specifications (ToolSpecs) 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:
ToolSpecs 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"ToolSpecs 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.stringToolSpecs for combinators for custom typesTo define ToolSpecs for your own custom types, use tools.customType,
tools.variant0, tools.variant1, tools.variant2, tools.variant3,
tools.variant4, tools.variant5, and tools.endCustomType.
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 variant =
case variant of
Some value ->
some value
None ->
none
in
tools.customType
{ toString = match, toComparable = match }
|> tools.variant1 "Some" Some valueSpec
|> tools.variant0 "None" None
|> tools.endCustomTypeNote: 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."
ToolSpecs 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.endRecordToolSpec 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 }"ToolSpectools.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 ToolSpecs 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.
defineBegin 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
ToStringtool and aToComparabletool, you could do something like this:Note: The order of the type parameters in the
Toolstype alias is important. It needs to match the order in which you add the tool interfaces withMultiTool.add.In this example, we see that
toStringcomes first andtoComparablecomes second in both theToolstype alias and the interfaces added totools.