elm-monocle
A Monocle-inspired library providing purely functional abstractions to manipulate complex records in the elm language.
Published as arturopala/elm-monocle library.
Long Example
import Monocle.Optional exposing (Optional)
import Monocle.Lens exposing (Lens)
import Monocle.Common exposing ((<|>), (=>))
type StreetType
= Street
| Avenue
type Country
= US
| UK
| FI
| PL
| DE
type alias Address =
{ streetName : String
, streetType : StreetType
, floor : Maybe Int
, town : String
, region : Maybe String
, postcode : String
, country : Country
}
type alias Place =
{ name : String
, description : Maybe String
, address : Maybe Address
}
addressOfPlace : Optional Place Address
addressOfPlace =
Optional .address (\b a -> { a | address = Just b })
regionOfAddress : Optional Address String
regionOfAddress =
Optional .region (\b a -> { a | region = Just b })
streetNameOfAddress : Lens Address String
streetNameOfAddress =
Lens .streetName (\b a -> { a | streetName = b })
regionOfPlace : Optional Place String
regionOfPlace =
addressOfPlace => regionOfAddress
streetNameOfPlace : Optional Place String
streetNameOfPlace =
Monocle.Optional.composeLens addressOfPlace streetNameOfAddress
place : Place
place =
{ name = "MyPlace"
, description = Nothing
, address =
Just
{ streetName = "Union Road"
, streetType = Street
, floor = Nothing
, town = "Daisytown"
, region = Nothing
, postcode = "00100"
, country = US
}
}
updatedPlace : Place
updatedPlace =
place
|> regionOfPlace.set "NorthEast"
|> streetNameOfPlace.set "Union Avenue"
Abstractions
Iso
An Iso is a tool which converts elements of type A into elements of type B and back without loss.
type alias Iso a b =
{ get : a -> b
, reverseGet : b -> a
}
Example
string2CharListIso : Iso String (List Char)
string2CharListIso =
Iso String.toList String.fromList
(string2CharListIso.get "ABcdE") == ['A','B','c','d','E']
(string2CharListIso.reverseGet ['A','B','c','d','E']) == "ABcdE"
Prism
A Prism is a tool which optionally converts elements of type A into elements of type B and back.
type alias Prism a b =
{ getOption : a -> Maybe b
, reverseGet : b -> a
}
Example
string2IntPrism : Prism String Int
string2IntPrism =
Prism (String.toInt >> Result.toMaybe) toString
string2IntPrism.getOption "17896" == Just 17896
string2IntPrism.getOption "1a896" == Nothing
string2IntPrism.reverseGet 1626767 = "1626767"
Lens
A Lens is a functional concept which solves a very common problem: how to easily update a complex immutable structure, for this purpose Lens acts as a zoom into a record.
type alias Lens a b =
{ get : a -> b
, set : b -> a -> a
}
Example
type alias Address =
{streetName: String
,postcode: String
,town: String}
type alias Place =
{name: String
,address: Address}
addressStreetNameLens : Lens Address String
addressStreetNameLens =
Lens .streetName (\b a -> { a | streetName = b })
placeAddressLens : Lens Place Address
placeAddressLens =
Lens .address (\b a -> { a | address = b })
placeStreetName: Lens Place String
placeStreetName = placeAddressLens <|> addressStreetNameLens
myPlace = Place "my" (Address "Elm" "00001" "Daisytown")
placeStreetName.get myPlace == "Elm"
myNewPlace = placeStreetName.set "Oak" myPlace
placeStreetName.get myNewPlace == "Oak"
myNewPlace == Place "my" (Address "Oak" "00001" "Daisytown")
Optional
A Optional is a weaker Lens and a weaker Prism.
type alias Optional a b =
{ getOption : a -> Maybe b
, set : b -> a -> a
}
Example
addressRegionOptional : Optional Address String
addressRegionOptional =
Optional .region (\b a -> { a | region = Just b })
string2IntPrism : Prism String Int
string2IntPrism = Prism (String.toInt >> Result.toMaybe) toString
addressRegionIntOptional: Optional Address Int
addressRegionIntOptional = addressRegionOptional => (fromPrism string2IntPrism)
string2CharListIso : Iso String (List Char)
string2CharListIso = Iso String.toList String.fromList
addressRegionListCharOptional: Optional Address (List Char)
addressRegionListCharOptional = composeLens addressRegionOptional (fromIso string2CharListIso)
modifyRegion: String -> String
modifyRegion region = String.reverse region
modifyAddressRegion: Address -> Maybe Address
modifyAddressRegion address = Optional.modifyOption addressRegionOptional modifyRegion address
modifyRegion: String -> String
modifyRegion region = String.reverse region
modifyAddressRegion: Address -> Address
modifyAddressRegion address = Optional.modify addressRegionOptional modifyRegion address
Common
Common lenses/prisms/optionals that most projects will use.
Convenient infix operator for composing lenses.
Allows to chain lens composition for deeply nested structures:
fromAtoB : Lens A B
fromAtoB = Lens .b (\b a -> { a | b = b })
fromBtoC : Lens B C
fromBtoC = Lens .c (\c b -> { b | c = c })
fromCtoD : Lens C D
fromCtoD = Lens .d (\d c -> { c | d = d })
fromDtoE : Lens D E
fromDtoE = Lens .e (\e d -> { d | e = e })
fromAtoE : Lens A E
fromAtoE = fromAtoB <|> fromBtoC <|> fromCtoD <|> fromDtoE
a : A
a = { b: { c: { d: { e: "Whatever we want to get" } } } }
fromAtoE.get a
=> "Whatever we want to get"
fromAtoE.set a "What we want to set"
=> { b: { c: { d: { e: "What we want to set" } } } }
Convenient infix operator for composing optionals.
.getOption (maybe => array 2) (Just <| Array.fromList [ 10, 11, 12, 13 ])
> 12
Convenient infix operator for composing optional with lens.
.getOption (maybe =|> id) (Just { id = 12 })
> 12
Maybe
value.
Step into a maybe.set 5 Nothing
> Just 5
Array
at the given index.
Step into an .getOption (array 2) (Array.fromList [ 10, 11, 12, 13 ])
> Just 12
.getOption (array 8) (Array.fromList [ 10, 11, 12, 13 ])
> Nothing
Dict
with the given key.
Step into a .getOption (dict "Tom") (Dict.fromList [ ( "Tom", "Cat" ) ])
> Just "Cat"
.getOption (dict "Jerry") (Dict.fromList [ ( "Tom", "Cat" ) ])
> Nothing
Result
.
Step into the success value of a result.getOption (Ok 5)
> Just 5
result.getOption (Err "500")
> Nothing
id
key.
Step into a record with an Since records with an id
field are incredible common, this is
included for convenience. It also serves as a simple recipe for
creating record lenses.
id.get { id = 1000, name = ... }
> Just 1000
Step into the first element of a pair.
first.get ( 'a', 'b' )
> Just 'a'
Step into the second element of a pair.
second.get ( 'a', 'b' )
> Just 'b'
Build
Prerequisites
- Node.js
- Yarn
- Run
yarn install-with-elm
Compile
Run yarn compile
Test
Run elm-test