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
, 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 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.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.endCustomNote: 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.