Repository: arturopala/elm-monocle Branch: master Commit: 7d8be090c9b7 Files: 22 Total size: 100.7 KB Directory structure: gitextract_bil9_ung/ ├── .gitignore ├── LICENSE ├── README.md ├── elm.json ├── example/ │ └── OptionalExample.elm ├── package.json ├── src/ │ └── Monocle/ │ ├── Common.elm │ ├── Compose.elm │ ├── Iso.elm │ ├── Lens.elm │ ├── Optional.elm │ ├── Prism.elm │ └── Traversal.elm └── tests/ ├── .gitignore ├── CommonSpec.elm ├── ComposeSpec.elm ├── IsoSpec.elm ├── LensSpec.elm ├── OptionalSpec.elm ├── PrismSpec.elm ├── Tests.elm └── TraversalSpec.elm ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # added by GitSavvy /elm-stuff/ # added by GitSavvy node_modules # added by GitSavvy /documentation.json # added by GitSavvy /monocle.js # added by GitSavvy /documentation.md /package-lock.json /yarn-error.log /yarn.lock ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Artur Opala Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Build Status](https://semaphoreci.com/api/v1/arturopala/elm-monocle/branches/master/badge.svg)](https://semaphoreci.com/arturopala/elm-monocle) elm-monocle =========== A [Monocle](http://optics-dev.github.io/Monocle/)-inspired library providing purely functional abstractions to manipulate complex records in the [elm](http://www.elm-lang.org/) language. Published as [**arturopala/elm-monocle**](http://package.elm-lang.org/packages/arturopala/elm-monocle/latest) library. # Long Example ```elm import Monocle.Optional exposing (Optional) import Monocle.Lens exposing (Lens) 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 |> Monocle.Compose.optionalWithOptional regionOfAddress streetNameOfPlace : Optional Place String streetNameOfPlace = addressOfPlace |> Monocle.Compose.optionalWithLens 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. ```elm type alias Iso a b = { get : a -> b , reverseGet : b -> a } ``` ###### Example ```elm 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. ```elm type alias Prism a b = { getOption : a -> Maybe b , reverseGet : b -> a } ``` ###### Example ```elm string2IntPrism : Prism String Int string2IntPrism = Prism String.toInt String.fromInt 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. ```elm type alias Lens a b = { get : a -> b , set : b -> a -> a } ``` ###### Example ```elm 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 |> Monocle.Compose.lensWithLens 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. ```elm type alias Optional a b = { getOption : a -> Maybe b , set : b -> a -> a } ``` ###### Example ```elm addressRegionOptional : Optional Address String addressRegionOptional = Optional .region (\b a -> { a | region = Just b }) string2IntPrism : Prism String Int string2IntPrism = Prism String.toInt String.fromInt addressRegionIntOptional: Optional Address Int addressRegionIntOptional = addressRegionOptional |> Monocle.Compose.optionalWithPrism string2IntPrism string2CharListIso : Iso String (List Char) string2CharListIso = Iso String.toList String.fromList addressRegionListCharOptional: Optional Address (List Char) addressRegionListCharOptional = addressRegionOptional |> Monocle.Compose.optionalWithIso 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 ``` ## Traversal A Traversal allows you to modify many elements at once. ```elm type alias Traversal a b = (b -> b) -> a -> a ``` (`Traversal a b` is just an alias for a function that applies a transformation over `b` elements of a larger `a` structure.) ###### Example ```elm firstNameLens : Lens Friend String firstNameLens = Lens .firstName (\b a -> { a | firstName = b }) bestFriendsTraversal : Traversal (List Friend) Friend bestFriendsTraversal = Traversal.some Traversal.list (\friend -> friend.value == Best) friendsLens : Lens Account (List Friend) friendsLens = Lens .friends (\b a -> { a | friends = b }) firstNamesOfBestFriends : Traversal Account String firstNamesOfBestFriends = friendsLens |> Compose.lensWithTraversal bestFriendsTraversal |> Compose.traversalWithLens firstNameLens upcaseBestFriendsFirstNames : Account -> Account upcaseBestFriendsFirstNames account = Traversal.modify firstNamesOfBestFriends String.toUpper ``` ## Common Common lenses/prisms/optionals that most projects will use. #### Step into a `Maybe` value. ```elm maybe.set 5 Nothing > Just 5 ``` #### Step into an `Array` at the given index. ```elm .getOption (array 2) (Array.fromList [ 10, 11, 12, 13 ]) > Just 12 .getOption (array 8) (Array.fromList [ 10, 11, 12, 13 ]) > Nothing ``` #### Step into a `Dict` with the given key. ```elm .getOption (dict "Tom") (Dict.fromList [ ( "Tom", "Cat" ) ]) > Just "Cat" .getOption (dict "Jerry") (Dict.fromList [ ( "Tom", "Cat" ) ]) > Nothing ``` #### Step into the success value of a `Result`. ```elm result.getOption (Ok 5) > Just 5 result.getOption (Err "500") > Nothing ``` #### Step into a record with an `id` key. 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. ```elm id.get { id = 1000, name = ... } > 1000 ``` #### Step into the first element of a pair. ```elm first.get ( 'a', 'b' ) > 'a' ``` #### Step into the second element of a pair. ```elm second.get ( 'a', 'b' ) > 'b' ``` # Build ## Prerequisites - Node.js - Yarn - Run `yarn install-with-elm` ## Compile Run `yarn compile` ## Test Run `elm-test` ================================================ FILE: elm.json ================================================ { "type": "package", "name": "arturopala/elm-monocle", "summary": "Library providing functional tools to manipulate complex records", "license": "MIT", "version": "2.2.0", "exposed-modules": [ "Monocle.Iso", "Monocle.Prism", "Monocle.Lens", "Monocle.Optional", "Monocle.Traversal", "Monocle.Common", "Monocle.Compose" ], "elm-version": "0.19.0 <= v < 0.20.0", "dependencies": { "elm/core": "1.0.0 <= v < 2.0.0" }, "test-dependencies": { "elm-explorations/test": "1.1.0 <= v < 1.1.1" } } ================================================ FILE: example/OptionalExample.elm ================================================ module OptionalExample exposing (Address, Country(..), Place, StreetType(..), addressOfPlace, place, regionOfAddress, regionOfPlace, streetNameOfAddress, streetNameOfPlace, updatedPlace) import Monocle.Lens exposing (Lens) import Monocle.Optional exposing (Optional) 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 = let getOption p = p.address set a p = { p | address = Just a } in Optional getOption set regionOfAddress : Optional Address String regionOfAddress = let getOption a = a.region set r a = { a | region = Just r } in Optional getOption set streetNameOfAddress : Lens Address String streetNameOfAddress = let get a = a.streetName set sn a = { a | streetName = sn } in Lens get set regionOfPlace : Optional Place String regionOfPlace = Monocle.Optional.compose 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" ================================================ FILE: package.json ================================================ { "name": "elm-monocle", "version": "1.6.0", "description": "Library providing purely functional abstractions to manipulate records", "scripts": { "compile": "elm make ./src/**/*.elm --output monocle.js", "install-with-elm": "yarn install && elm make", "test": "elm-test", "test-and-watch": "elm-test --watch", "bump": "elm bump", "publish": "elm publish" }, "repository": { "type": "git", "url": "git+https://github.com/arturopala/elm-monocle.git" }, "keywords": [ "monocle", "lens", "iso", "prism", "optional", "record" ], "author": "Artur Opala", "license": "MIT", "bugs": { "url": "https://github.com/arturopala/elm-monocle/issues" }, "homepage": "https://github.com/arturopala/elm-monocle#readme", "devDependencies": { "elm": "^0.19.0", "elm-test": "0.19.0-beta9" } } ================================================ FILE: src/Monocle/Common.elm ================================================ module Monocle.Common exposing ( maybe , array , list , listToArray , dict , result , id , first , second ) {-| Common lenses/prisms/optionals that most projects will use. @docs maybe @docs array @docs list @docs listToArray @docs dict @docs result @docs id @docs first @docs second -} import Array exposing (Array) import Dict exposing (Dict) import Monocle.Iso exposing (Iso) import Monocle.Lens as Lens exposing (Lens) import Monocle.Optional as Optional exposing (Optional) {-| Step into a `Maybe` value. maybe.set 5 Nothing > Just 5 -} maybe : Optional (Maybe a) a maybe = { getOption = identity , set = always << Just } {-| Step into an `Array` at the given index. .getOption (array 2) (Array.fromList [ 10, 11, 12, 13 ]) > Just 12 .getOption (array 8) (Array.fromList [ 10, 11, 12, 13 ]) > Nothing -} array : Int -> Optional (Array a) a array index = { getOption = Array.get index , set = Array.set index } {-| Step into an `List` at the given index. (shortcut to avoid converting a `List` to an `Array` ; if it is too slow, consider using `array` and an `Array` instead of a `List` in your data) .getOption (list 2) [ 10, 11, 12, 13 ] > Just 12 .getOption (list 8) [ 10, 11, 12, 13 ] > Nothing -} list : Int -> Optional (List a) a list index = Optional.compose (Optional.fromLens (Lens.fromIso listToArray)) (array index) {-| Iso that converts a list to an array. .get listToArray [ 1, 2, 3, 4 ] == Array.fromList [ 1, 2, 3, 4 ] > True .reverseGet listToArray (Array.fromList [ 9, 8, 7, 6 ]) == [ 9, 8, 7, 6 ] > True -} listToArray : Iso (List a) (Array a) listToArray = Iso Array.fromList Array.toList {-| Step into a `Dict` with the given key. .getOption (dict "Tom") (Dict.fromList [ ( "Tom", "Cat" ) ]) > Just "Cat" .getOption (dict "Jerry") (Dict.fromList [ ( "Tom", "Cat" ) ]) > Nothing -} dict : comparable -> Optional (Dict comparable v) v dict key = { getOption = Dict.get key , set = Dict.insert key } {-| Step into the success value of a `Result`. result.getOption (Ok 5) > Just 5 result.getOption (Err "500") > Nothing -} result : Optional (Result e a) a result = { getOption = Result.toMaybe , set = always << Ok } {-| Step into a record with an `id` key. id.get { id = 1000, name = ... } > 1000 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 : Lens { a | id : b } b id = { get = .id , set = \a record -> { record | id = a } } {-| Step into the first element of a pair. first.get ( 'a', 'b' ) > 'a' -} first : Lens ( a, b ) a first = { get = Tuple.first , set = \a ( _, b ) -> ( a, b ) } {-| Step into the second element of a pair. second.get ( 'a', 'b' ) > 'b' -} second : Lens ( a, b ) b second = { get = Tuple.second , set = \b ( a, _ ) -> ( a, b ) } ================================================ FILE: src/Monocle/Compose.elm ================================================ module Monocle.Compose exposing ( isoWithIso, isoWithPrism, isoWithLens, isoWithOptional, isoWithTraversal , prismWithIso, prismWithPrism, prismWithLens, prismWithOptional, prismWithTraversal , lensWithIso, lensWithPrism, lensWithLens, lensWithOptional, lensWithTraversal , optionalWithIso, optionalWithPrism, optionalWithLens, optionalWithOptional, optionalWithTraversal , traversalWithIso, traversalWithPrism, traversalWithLens, traversalWithOptional, traversalWithTraversal ) {-| Pipeline-friendly composition helpers Using these allow to compose an "outer" optic with an "inner" other optic. Optics in functional programming languages that support typeclasses can be expressed as functions that compose through the composition operator (just like any other functions) ; in Elm (plus typeclasses), it would look like this: lensAtoB >> lensBtoC >> lensCtoD == lensAtoD lensAtoB >> optionalBtoC >> prismCtoD == optionalAtoC But Elm doesn't support typeclasses, so we're stuck with defining composition functions that look similar like this: import Monocle.Compose as Compose lensAtoB |> Compose.lensWithLens lensBtoC |> Compose.lensWithLens lensCtoD == lensAtoD lensAtoB |> Compose.lensWithOptional optionalBtoC |> Compose.optionalWithPrism prismCtoD == optionalAtoC This is arguably more "discoverable" and maybe more readable, if more verbose. # From an Iso @docs isoWithIso, isoWithPrism, isoWithLens, isoWithOptional, isoWithTraversal # From a Prism @docs prismWithIso, prismWithPrism, prismWithLens, prismWithOptional, prismWithTraversal # From a Lens @docs lensWithIso, lensWithPrism, lensWithLens, lensWithOptional, lensWithTraversal # From an Optional @docs optionalWithIso, optionalWithPrism, optionalWithLens, optionalWithOptional, optionalWithTraversal # From a Traversal @docs traversalWithIso, traversalWithPrism, traversalWithLens, traversalWithOptional, traversalWithTraversal -} import Monocle.Iso as Iso exposing (Iso) import Monocle.Lens as Lens exposing (Lens) import Monocle.Optional as Optional exposing (Optional) import Monocle.Prism as Prism exposing (Prism) import Monocle.Traversal as Traversal exposing (Traversal) {-| pipeline-friendly composition between two Iso ab : Iso A B ab = .. bc : Iso B C bc = .. ac : Iso A C ac = ab |> ComposeIso.isoWithIso bc -} isoWithIso : Iso b c -> Iso a b -> Iso a c isoWithIso inner outer = Iso.compose outer inner {-| pipeline-friendly composition between an outer Iso and an inner Prism (the result is a Prism) ab : Iso A B ab = .. bc : Prism B C bc = .. ac : Prism A C ac = ab |> ComposeIso.isoWithPrism bc -} isoWithPrism : Prism b c -> Iso a b -> Prism a c isoWithPrism inner outer = let getOption = outer.get >> inner.getOption reverseGet = inner.reverseGet >> outer.reverseGet in Prism getOption reverseGet {-| pipeline-friendly composition between an outer Iso and an inner Traversal (the result is a Traversal) ab : Iso A B ab = .. bc : Traversal B C bc = .. ac : Traversal A C ac = ab |> ComposeIso.isoWithTraversal bc -} isoWithTraversal : Traversal b c -> Iso a b -> Traversal a c isoWithTraversal inner outer transformation = Iso.modify outer (Traversal.modify inner transformation) {-| pipeline-friendly composition between an outer Iso and an inner Lens (the result is a Lens) ab : Iso A B ab = .. bc : Lens B C bc = .. ac : Lens A C ac = ab |> ComposeIso.isoWithLens bc -} isoWithLens : Lens b c -> Iso a b -> Lens a c isoWithLens inner outer = let get = outer.get >> inner.get set c = Iso.modify outer (inner.set c) in Lens get set {-| pipeline-friendly composition between an outer Iso and an inner Optional (the result is an Optional) ab : Iso A B ab = .. bc : Optional B C bc = .. ac : Optional A C ac = ab |> ComposeIso.isoWithOptional bc -} isoWithOptional : Optional b c -> Iso a b -> Optional a c isoWithOptional inner outer = let getOption = outer.get >> inner.getOption set c = Iso.modify outer (inner.set c) in Optional getOption set {-| pipeline-friendly composition between an outer Prism and an inner Iso (the result is a Prism) ab : Prism A B ab = .. bc : Iso B C bc = .. ac : Prism A C ac = ab |> ComposePrism.prismWithIso bc -} prismWithIso : Iso b c -> Prism a b -> Prism a c prismWithIso inner outer = let getOption = outer.getOption >> Maybe.map inner.get reverseGet = inner.reverseGet >> outer.reverseGet in Prism getOption reverseGet {-| pipeline-friendly composition between two Prisms ab : Prism A B ab = .. bc : Prism B C bc = .. ac : Prism A C ac = ab |> ComposePrism.prismWithPrism bc -} prismWithPrism : Prism b c -> Prism a b -> Prism a c prismWithPrism inner outer = Prism.compose outer inner {-| pipeline-friendly composition between an outer Prism and an inner Lens (the result is an Optional) ab : Prism A B ab = .. bc : Lens B C bc = .. ac : Optional A C ac = ab |> ComposePrism.prismWithLens bc -} prismWithLens : Lens b c -> Prism a b -> Optional a c prismWithLens inner outer = let getOption = outer.getOption >> Maybe.map inner.get set c = Prism.modify outer (inner.set c) in Optional getOption set {-| pipeline-friendly composition between an outer Prism and an inner Optional (the result is an Optional) ab : Prism A B ab = .. bc : Optional B C bc = .. ac : Optional A C ac = ab |> ComposePrism.prismWithOptional bc -} prismWithOptional : Optional b c -> Prism a b -> Optional a c prismWithOptional inner outer = let getOption = outer.getOption >> Maybe.andThen inner.getOption set c = Prism.modify outer (inner.set c) in Optional getOption set {-| pipeline-friendly composition between an outer Prism and an inner Traversal (the result is a Traversal) ab : Prism A B ab = .. bc : Traversal B C bc = .. ac : Traversal A C ac = ab |> ComposePrism.prismWithTraversal bc -} prismWithTraversal : Traversal b c -> Prism a b -> Traversal a c prismWithTraversal inner outer transformation = Prism.modify outer (Traversal.modify inner transformation) {-| pipeline-friendly composition between an outer Lens and an inner Iso (the result is a Lens) ab : Lens A B ab = .. bc : Iso B C bc = .. ac : Lens A C ac = ab |> Compose.lensWithIso bc -} lensWithIso : Iso b c -> Lens a b -> Lens a c lensWithIso inner outer = let get = outer.get >> inner.get set c = outer.set (inner.reverseGet c) in Lens get set {-| pipeline-friendly composition between an outer Lens and an inner Prism (the result is an Optional) ab : Lens A B ab = .. bc : Prism B C bc = .. ac : Optional A C ac = ab |> Compose.lensWithPrism bc -} lensWithPrism : Prism b c -> Lens a b -> Optional a c lensWithPrism inner outer = let getOption = outer.get >> inner.getOption set c = outer.set (inner.reverseGet c) in Optional getOption set {-| pipeline-friendly composition between two Lenses ab : Lens A B ab = .. bc : Lens B C bc = .. ac : Lens A C ac = ab |> Compose.lensWithLens bc -} lensWithLens : Lens b c -> Lens a b -> Lens a c lensWithLens inner outer = Lens.compose outer inner {-| pipeline-friendly composition between an outer Lens and an inner Optional (the result is an Optional) ab : Lens A B ab = .. bc : Optional B C bc = .. ac : Optional A C ac = ab |> Compose.lensWithOptional bc -} lensWithOptional : Optional b c -> Lens a b -> Optional a c lensWithOptional inner outer = let getOption = outer.get >> inner.getOption set c = Lens.modify outer (inner.set c) in Optional getOption set {-| pipeline-friendly composition between an outer Lens and an inner Traversal (the result is an Traversal) ab : Lens A B ab = .. bc : Traversal B C bc = .. ac : Traversal A C ac = ab |> Compose.lensWithTraversal bc -} lensWithTraversal : Traversal b c -> Lens a b -> Traversal a c lensWithTraversal inner outer transformation = Lens.modify outer (Traversal.modify inner transformation) {-| pipeline-friendly composition between an outer Optional and an inner Iso (the result is an Optional) ab : Optional A B ab = .. bc : Iso B C bc = .. ac : Optional A C ac = ab |> ComposeOptional.optionalWithIso bc -} optionalWithIso : Iso b c -> Optional a b -> Optional a c optionalWithIso inner outer = let getOption = outer.getOption >> Maybe.map inner.get set c = outer.set (inner.reverseGet c) in Optional getOption set {-| pipeline-friendly composition between an outer Optional and an inner Prism (the result is an Optional) ab : Optional A B ab = .. bc : Prism B C bc = .. ac : Optional A C ac = ab |> ComposeOptional.withPrism bc -} optionalWithPrism : Prism b c -> Optional a b -> Optional a c optionalWithPrism inner outer = let getOption = outer.getOption >> Maybe.andThen inner.getOption set c = outer.set (inner.reverseGet c) in Optional getOption set {-| pipeline-friendly composition between an outer Optional and an inner Lens (the result is an Optional) ab : Optional A B ab = .. bc : Lens B C bc = .. ac : Optional A C ac = ab |> ComposeOptional.optionalWithLens bc -} optionalWithLens : Lens b c -> Optional a b -> Optional a c optionalWithLens inner outer = let getOption = outer.getOption >> Maybe.map inner.get set c = Optional.modify outer (inner.set c) in Optional getOption set {-| pipeline-friendly composition between two Optionals ab : Optional A B ab = .. bc : Optional B C bc = .. ac : Optional A C ac = ab |> ComposeOptional.optionalWithOptional bc -} optionalWithOptional : Optional b c -> Optional a b -> Optional a c optionalWithOptional inner outer = Optional.compose outer inner {-| pipeline-friendly composition between an outer Optional and an inner Traversal (the result is a Optional) ab : Optional A B ab = .. bc : Traversal B C bc = .. ac : Optional A C ac = ab |> ComposeOptional.optionalWithTraversal bc -} optionalWithTraversal : Traversal b c -> Optional a b -> Traversal a c optionalWithTraversal inner outer transformation = Optional.modify outer (Traversal.modify inner transformation) {-| pipeline-friendly composition between an outer Traversal and an inner Iso (the result is a Traversal) ab : Traversal A B ab = .. bc : Iso B C bc = .. ac : Traversal A C ac = ab |> ComposeTraversal.traversalWithIso bc -} traversalWithIso : Iso b c -> Traversal a b -> Traversal a c traversalWithIso inner outer transformation = Traversal.modify outer (Iso.modify inner transformation) {-| pipeline-friendly composition between an outer Traversal and an inner Lens (the result is a Traversal) ab : Traversal A B ab = .. bc : Lens B C bc = .. ac : Traversal A C ac = ab |> ComposeTraversal.traversalWithLens bc -} traversalWithLens : Lens b c -> Traversal a b -> Traversal a c traversalWithLens inner outer transformation = Traversal.modify outer (Lens.modify inner transformation) {-| pipeline-friendly composition between an outer Traversal and an inner Optional (the result is a Traversal) ab : Traversal A B ab = .. bc : Optional B C bc = .. ac : Traversal A C ac = ab |> ComposeTraversal.traversalWithOptional bc -} traversalWithOptional : Optional b c -> Traversal a b -> Traversal a c traversalWithOptional inner outer transformation = Traversal.modify outer (Optional.modify inner transformation) {-| pipeline-friendly composition between an outer Traversal and an inner Prism (the result is a Traversal) ab : Traversal A B ab = .. bc : Prism B C bc = .. ac : Traversal A C ac = ab |> ComposeTraversal.traversalWithPrism bc -} traversalWithPrism : Prism b c -> Traversal a b -> Traversal a c traversalWithPrism inner outer transformation = Traversal.modify outer (Prism.modify inner transformation) {-| pipeline-friendly composition between two Traversals (the result is a Traversal) ab : Traversal A B ab = .. bc : Traversal B C bc = .. ac : Traversal A C ac = ab |> ComposeTraversal.traversalWithTraversal bc -} traversalWithTraversal : Traversal b c -> Traversal a b -> Traversal a c traversalWithTraversal inner outer transformation = Traversal.modify outer (Traversal.modify inner transformation) ================================================ FILE: src/Monocle/Iso.elm ================================================ module Monocle.Iso exposing ( Iso , reverse, modify, compose ) {-| An Iso is a tool which converts elements of type A into elements of type B and back without loss. # Definition @docs Iso # Laws Identity: \x -> iso.get(iso.reverseGet x) == x Reversed: \x -> iso.reverseGet(iso.get x) == x # 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" # Derived methods @docs reverse, modify, compose -} {-| In order to create an `Iso` we need to supply two total functions: `get` and `reverseGet` -} type alias Iso a b = { get : a -> b , reverseGet : b -> a } {-| Creates reversed `Iso b a`, exchanges functions `get` and `reverseGet` .get (Iso.reversed someiso) == someiso.reverseGet .reverseGet (Iso.reversed someiso) == someiso.get Iso.compose someiso (Iso.reversed someiso) == Iso identity identity -} reverse : Iso a b -> Iso b a reverse iso = Iso iso.reverseGet iso.get {-| Modifies given function `(b -> b)` to be `(a -> a)` using `Iso a b` someiso = Iso String.toList String.fromList somefx xs = '@' :: xs modified = Iso.modify someiso somefx (modified "artur") == "@artur" -} modify : Iso a b -> (b -> b) -> a -> a modify iso f = iso.get >> f >> iso.reverseGet {-| Composes `Iso a b` with `Iso b c` and returns `Iso a c` -} compose : Iso a b -> Iso b c -> Iso a c compose outer inner = Iso (outer.get >> inner.get) (inner.reverseGet >> outer.reverseGet) ================================================ FILE: src/Monocle/Lens.elm ================================================ module Monocle.Lens exposing ( Lens , compose, modify, modify2, modify3, modifyAndMerge, zip, tuple, tuple3 , fromIso ) {-| 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. # Definition @docs Lens # Example addressStreetNameLens : Lens Address String addressStreetNameLens = let get a = a.streetName set sn a = { a | streetName = sn } in Lens get set placeAddressLens : Lens Place Address placeAddressLens = let get p = p.address set a p = { p | address = a } in Lens get set placeStreetName : Lens Place String placeStreetName = compose placeAddressLens addressStreetNameLens # Derived methods @docs compose, modify, modify2, modify3, modifyAndMerge, zip, tuple, tuple3 # Conversion @docs fromIso -} import Dict exposing (Dict) import Monocle.Iso exposing (Iso) {-| In order to create Lens we need to supply 2 functions: set and get -} type alias Lens a b = { get : a -> b , set : b -> a -> a } {-| Composes `Lens a b` with `Lens b c` and returns `Lens a c` -} compose : Lens a b -> Lens b c -> Lens a c compose outer inner = let set c a = outer.get a |> inner.set c |> (\b -> outer.set b a) in Lens (outer.get >> inner.get) set {-| Modifies given function `(b -> b)` to be `(a -> a)` using `Lens a b` addressStreetNameLens = Lens Address String fx streetName = String.reverse streetName fx2 = Lens.modify addressStreetNameLens fx fx2 {streetName="abcdef"} == {streetName="fedcba"} -} modify : Lens a b -> (b -> b) -> a -> a modify lens f = let mf a = lens.get a |> f |> (\b -> lens.set b a) in mf {-| Modifies given function `(b,d) -> (b,d)` to be `(a,c) -> (a,c)` using `Lens a b` and `Lens c d` -} modify2 : Lens a b -> Lens c d -> (( b, d ) -> ( b, d )) -> ( a, c ) -> ( a, c ) modify2 lens1 lens2 fx = let mf ( a, c ) = ( lens1.get a, lens2.get c ) |> fx |> (\( b, d ) -> ( lens1.set b a, lens2.set d c )) in mf {-| Modifies given function `(b,d,f) -> (b,d,f)` to be `(a,c,e) -> (a,c,e)` using `Lens a b` and `Lens c d` and `Lens e f` -} modify3 : Lens a b -> Lens c d -> Lens e f -> (( b, d, f ) -> ( b, d, f )) -> ( a, c, e ) -> ( a, c, e ) modify3 lens1 lens2 lens3 fx = let mf ( a, c, e ) = ( lens1.get a, lens2.get c, lens3.get e ) |> fx |> (\( b, d, f ) -> ( lens1.set b a, lens2.set d c, lens3.set f e )) in mf {-| Casts `Iso a b` to `Lens a b` -} fromIso : Iso a b -> Lens a b fromIso iso = let set b _ = iso.reverseGet b in Lens iso.get set {-| Zips `Lens a c` with `Lens b d` to form Lens ( a, b ) ( c, d ) -} zip : Lens a c -> Lens b d -> Lens ( a, b ) ( c, d ) zip left right = let get ( a, b ) = ( left.get a, right.get b ) set ( c, d ) ( a, b ) = ( left.set c a, right.set d b ) in Lens get set {-| Modifies given function `(b -> (b,c))` to be `(a,c) -> (a,c)` using `Lens a b` and `merge` function -} modifyAndMerge : Lens a b -> (b -> ( b, c )) -> (c -> c -> c) -> ( a, c ) -> ( a, c ) modifyAndMerge lens fx merge = let mf ( a, c ) = lens.get a |> fx |> (\( b, c1 ) -> ( lens.set b a, merge c c1 )) in mf {-| Tuple `Lens a b` with `Lens a c` and returns `Lens a (b,c)` -} tuple : Lens a b -> Lens a c -> Lens a ( b, c ) tuple left right = let get a = ( left.get a, right.get a ) set ( b, c ) a = right.set c (left.set b a) in Lens get set {-| Tuple `Lens a b` with `Lens a c` with `Lens a d` and returns `Lens a (b,c,d)` -} tuple3 : Lens a b -> Lens a c -> Lens a d -> Lens a ( b, c, d ) tuple3 first second third = let get a = ( first.get a, second.get a, third.get a ) set ( b, c, d ) a = third.set d (second.set c (first.set b a)) in Lens get set ================================================ FILE: src/Monocle/Optional.elm ================================================ module Monocle.Optional exposing ( Optional , compose, composeLens, modifyOption, modify, modify2, modify3, zip, tuple, tuple3 , fromPrism, fromLens ) {-| A Optional is a weaker Lens and a weaker Prism # Definition @docs Optional # Derived methods @docs compose, composeLens, modifyOption, modify, modify2, modify3, zip, tuple, tuple3 # Conversion @docs fromPrism, fromLens # Example addressRegionOptional : Optional Address String addressRegionOptional = let getOption a = a.region set r a = { a | region = Just r } in Optional getOption set -} import Monocle.Lens exposing (Lens) import Monocle.Prism exposing (Prism) flip : (a -> b -> c) -> b -> a -> c flip f b a = f a b {-| In order to create Optional we need to supply 2 functions: set and getOption -} type alias Optional a b = { getOption : a -> Maybe b , set : b -> a -> a } {-| Composes `Optional a b` with `Optional b c` and returns `Optional a c` string2IntPrism : Prism String Int string2IntPrism = Prism String.toInt String.fromInt addressRegionIntOptional : Optional Address Int addressRegionIntOptional = compose addressRegionOptional (fromPrism string2IntPrism) -} compose : Optional a b -> Optional b c -> Optional a c compose outer inner = let set c a = outer.getOption a |> Maybe.map (inner.set c >> flip outer.set a) |> Maybe.withDefault a getOption a = case outer.getOption a of Just x -> x |> inner.getOption Nothing -> Nothing in Optional getOption set {-| Composes `Optional a b` with `Lens b c` and returns `Optional a c` string2CharListIso : Iso String (List Char) string2CharListIso = Iso String.toList String.fromList addressRegionListCharOptional : Optional Address (List Char) addressRegionListCharOptional = composeLens addressRegionOptional (fromIso string2CharListIso) -} composeLens : Optional a b -> Lens b c -> Optional a c composeLens opt lens = let set c a = opt.getOption a |> Maybe.map (lens.set c >> flip opt.set a) |> Maybe.withDefault a getOption a = case opt.getOption a of Just b -> b |> lens.get |> Just Nothing -> Nothing in Optional getOption set {-| Modifies given function `(b -> b)` to be `(a -> Maybe a)` using `Optional a b` modifyRegion: String -> String modifyRegion region = String.reverse region modifyAddressRegion: Address -> Maybe Address modifyAddressRegion address = Optional.modifyOption addressRegionOptional modifyRegion address -} modifyOption : Optional a b -> (b -> b) -> a -> Maybe a modifyOption opt fx = let mf a = opt.getOption a |> Maybe.map (fx >> flip opt.set a) in mf {-| Modifies given function `(b -> b)` to be `(a -> a)` using `Optional a b` modifyRegion: String -> String modifyRegion region = String.reverse region modifyAddressRegion: Address -> Address modifyAddressRegion address = Optional.modify addressRegionOptional modifyRegion address -} modify : Optional a b -> (b -> b) -> a -> a modify opt fx = let mf a = modifyOption opt fx a |> Maybe.withDefault a in mf {-| Modifies given function `(b,d) -> (b,d)` to be `(a,c) -> (a,c)` using `Optional a b` and `Optional c d` Function will be invoked ONLY when for ALL arguments `a` and `c` method `Optional.getOption` returns some value. -} modify2 : Optional a b -> Optional c d -> (( b, d ) -> ( b, d )) -> ( a, c ) -> ( a, c ) modify2 opt1 opt2 fx = let mf ( a, c ) = case ( opt1.getOption a, opt2.getOption c ) of ( Just b, Just d ) -> ( b, d ) |> fx |> (\( b1, d1 ) -> ( opt1.set b1 a, opt2.set d1 c )) _ -> ( a, c ) in mf {-| Modifies given function `( b, d, f ) -> ( b, d, f )` to be `( a, c, e ) -> ( a, c, e )` using `Optional a b` and `Optional c d` and `Optional e f` Function will be invoked ONLY when for ALL arguments `a`,`c`,`f` method `Optional.getOption` returns some value. -} modify3 : Optional a b -> Optional c d -> Optional e f -> (( b, d, f ) -> ( b, d, f )) -> ( a, c, e ) -> ( a, c, e ) modify3 opt1 opt2 opt3 fx = let mf ( a, c, e ) = case ( opt1.getOption a, opt2.getOption c, opt3.getOption e ) of ( Just b, Just d, Just f ) -> ( b, d, f ) |> fx |> (\( b1, d1, f1 ) -> ( opt1.set b1 a, opt2.set d1 c, opt3.set f1 e )) _ -> ( a, c, e ) in mf {-| Casts `Prism a b` to `Optional a b` string2IntPrism : Prism String Int string2IntPrism = Prism String.toInt String.fromInt stringIntOptional : Optional String Int stringIntOptional = fromPrism string2IntPrism -} fromPrism : Prism a b -> Optional a b fromPrism prism = let set b _ = prism.reverseGet b in Optional prism.getOption set {-| Casts `Lens a b` to `Optional a b` where `getOption` will return always `Just` -} fromLens : Lens a b -> Optional a b fromLens lens = let getOption a = Just (lens.get a) in Optional getOption lens.set {-| Zip `Optional a c` with `Optional b d` to form Optional for the pairs ( a, b ) ( c, d ) -} zip : Optional a c -> Optional b d -> Optional ( a, b ) ( c, d ) zip left right = let getOption ( a, b ) = left.getOption a |> Maybe.andThen (\c -> right.getOption b |> Maybe.map (\d -> ( c, d )) ) set ( c, d ) ( a, b ) = ( left.set c a, right.set d b ) in Optional getOption set {-| Tuple `Optional a b` with `Optional a c` and returns `Optional a (b,c)` Method `Optional.getOption` returns pair of values only when both given optionals return some value. -} tuple : Optional a b -> Optional a c -> Optional a ( b, c ) tuple left right = let getOption a = case ( left.getOption a, right.getOption a ) of ( Just b, Just d ) -> Just ( b, d ) _ -> Nothing set ( b, c ) a = right.set c (left.set b a) in Optional getOption set {-| Tuple `Optional a b` with `Optional a c` with `Optional a d` and returns `Optional a (b,c,d)` Method `Optional.getOption` returns triple of values only when all given optionals return some value. -} tuple3 : Optional a b -> Optional a c -> Optional a d -> Optional a ( b, c, d ) tuple3 first second third = let getOption a = case ( first.getOption a, second.getOption a, third.getOption a ) of ( Just b, Just d, Just f ) -> Just ( b, d, f ) _ -> Nothing set ( b, c, d ) a = first.set b a |> second.set c |> third.set d in Optional getOption set ================================================ FILE: src/Monocle/Prism.elm ================================================ module Monocle.Prism exposing ( Prism , isMatching, modify, modifyOption, compose, composeIso , fromIso ) {-| A Prism is a tool which optionally converts elements of type A into elements of type B and back. # Definition @docs Prism # Example string2IntPrism : Prism String Int string2IntPrism = Prism String.toInt String.fromInt string2IntPrism.getOption "17896" == Just 17896 string2IntPrism.getOption "1a896" == Nothing string2IntPrism.reverseGet 1626767 = "1626767" # Derived methods @docs isMatching, modify, modifyOption, compose, composeIso # Conversion @docs fromIso -} import Maybe import Monocle.Iso exposing (Iso) {-| In order to create a `Prism` we need to supply two functions: `getOption` and `reverseGet` -} type alias Prism a b = { getOption : a -> Maybe b , reverseGet : b -> a } {-| Checks if value of type `A` has matching element of type 'B' Monocle.Prism.isMatching string2IntPrism "abc" == False Monocle.Prism.isMatching string2IntPrism "123" == True -} isMatching : Prism a b -> a -> Bool isMatching prism a = case prism.getOption a of Just c -> True Nothing -> False {-| Modifies given function `(b -> b)` to be `(a -> Maybe a)` using `Prism a b` fx i = i * 2 modified = Monocle.Prism.modify string2IntPrism fx modified "22" == Just "44" modified "abc" == Nothing -} modifyOption : Prism a b -> (b -> b) -> a -> Maybe a modifyOption prism f = prism.getOption >> Maybe.map (f >> prism.reverseGet) {-| Modifies given function `(b -> b)` to be `(a -> a)` using `Prism a b` fx i = i * 2 modified = Monocle.Prism.modify string2IntPrism fx modified "22" == "44" modified "abc" == "abc" -} modify : Prism a b -> (b -> b) -> a -> a modify prism f = let m x = modifyOption prism f x |> Maybe.withDefault x in m {-| Composes `Prism a b` with `Prism b c` and returns `Prism a c` prism = Monocle.Prism.compose string2FloatPrism float2IntPrism prism.getOption "22" == Just 22 prism.getOption "22.2" == Nothing prism.getOption "22a" == Nothing prism.getOption "abc" == Nothing -} compose : Prism a b -> Prism b c -> Prism a c compose outer inner = let getOption x = case outer.getOption x of Just y -> y |> inner.getOption Nothing -> Nothing in Prism getOption (inner.reverseGet >> outer.reverseGet) {-| Composes `Prism a b` with `Iso b c` and returns `Prism a c` iso = Iso ((*) 10) ((//) 10) prism = Monocle.Prism.composeIso string2IntPrism iso prism.getOption "22" == Just 220 prism.getOption "22.2" == Nothing prism.getOption "22a" == Nothing prism.getOption "abc" == Nothing -} composeIso : Prism a b -> Iso b c -> Prism a c composeIso outer inner = let getOption x = case outer.getOption x of Just y -> y |> inner.get |> Just Nothing -> Nothing in Prism getOption (inner.reverseGet >> outer.reverseGet) {-| Casts `Iso a b` to `Prism a b` -} fromIso : Iso a b -> Prism a b fromIso iso = Prism (iso.get >> Just) iso.reverseGet ================================================ FILE: src/Monocle/Traversal.elm ================================================ module Monocle.Traversal exposing ( Traversal , list, array, some, modify ) {-| A Traversal is like an Optional that may modify multiple sub-elements, keeping the overlaying structure as is. # Definition @docs Traversal # Example personsAge : Lens Person Int personsAge = let get a = a.age set b a = { a | age = b } in Lens get set jeffs : Traversal (List Person) Person jeffs = Traversal.some Traversal.list (\p -> p.name == "Jeff") jeffsAges : Traversal (List Person) Int jeffsAges = jeffs |> Compose.traversalWithLens personsAge # Derived methods @docs list, array, some, modify -} import Array exposing (Array) {-| To create a Traversal, you just need to provide a function that applies a transformation function to some or all elements in the parent structure. -} type alias Traversal a b = (b -> b) -> a -> a {-| A basic traversal that affects all elements in a list -} list : Traversal (List a) a list = List.map {-| A basic traversal that affects all elements in an array -} array : Traversal (Array a) a array = Array.map {-| A traversal that, given another traversal as base, only affects those traversed elements that satisfy a condition. numbers : Traversal (List number) number numbers = Traversal.list oddNumbers : Traversal (List number) number oddNumbers = Traversal.some numbers (\number -> remainderBy 2 number == 1) evenNumbers : Traversal (List number) number evenNumbers = Traversal.some numbers (\number -> remainderBy 2 number == 0) -} some : Traversal a b -> (b -> Bool) -> Traversal a b some traversal condition transformation = let conditionedTransformation value = if condition value then transformation value else value in traversal conditionedTransformation {-| Modifies all elements traversed by `Traversal a b` using function `(b -> b)` in structure `a` personsHairColor : Lens Person String personsHairColor = Lens .hairColor (\b a-> { a | hairColor = b }) pauls : Traversal (List Person) Person pauls = Traversal.some Traversal.list (\person -> person.firstName == 'Paul' ) paulsHairColor : Traversal (List Person) String paulsHairColor = pauls |> Compose.traversalWithLens personsHairColor lighten : String -> String lighten hairColor = "light " ++ hairColor lightenPauls : List Person -> List Person lightenPauls = Traversal.modify pauls lightenPauls [ { firstName = "Paul" , hairColor = "brown" }, { firstName = "Jake" , hairColor = "blond" } ] == [ { firstName = "Paul" , hairColor = "light brown" }, { firstName = "Jake" , hairColor = "blond" } ] -} modify : Traversal a b -> (b -> b) -> a -> a modify traversal = traversal ================================================ FILE: tests/.gitignore ================================================ /elm-stuff/ ================================================ FILE: tests/CommonSpec.elm ================================================ module CommonSpec exposing (..) import Test exposing (..) import Expect import Fuzz exposing (int, tuple, string, char) import Dict import Maybe import Array import Monocle.Common exposing (dict, maybe, array, list, listToArray, id) all : Test all = describe "A Common specification" [ test_maybe , test_array_just , test_array_nothing , test_list_just , test_list_nothing , test_list_to_array_get , test_list_to_array_reverse_get , test_dict_empty , test_dict_list ] test_maybe : Test test_maybe = let test s = maybe.set s Nothing |> Expect.equal (Just s) in fuzz string "Common.maybe should step into Maybe" test test_array_just : Test test_array_just = let test i = .getOption (array 2) (Array.fromList [ 10, 11, i, 13 ]) |> Expect.equal (Just i) in fuzz int "Common.array should get some value at position 2" test test_array_nothing : Test test_array_nothing = let test i = .getOption (array 8) (Array.fromList [ i, i, i, i, i, i, i, i ]) |> Expect.equal Nothing in fuzz int "Common.array should return nothing if index out of bound" test test_list_just : Test test_list_just = let test i = .getOption (list 2) [ 10, 11, i, 13 ] |> Expect.equal (Just i) in fuzz int "Common.list should get some value at position 2" test test_list_nothing : Test test_list_nothing = let test i = .getOption (list 8) [ i, i, i, i, i, i, i, i ] |> Expect.equal Nothing in fuzz int "Common.list should return nothing if index out of bound" test test_list_to_array_get : Test test_list_to_array_get = let test l = .get listToArray l |> Expect.equal (l |> Array.fromList) in fuzz (Fuzz.list int) "Common.listToArray.get should convert a list to an array" test test_list_to_array_reverse_get : Test test_list_to_array_reverse_get = let test a = .reverseGet listToArray a |> Expect.equal (Array.toList a) in fuzz (Fuzz.array int) "Common.listToArray.reverseGet should convert an array to a list" test test_dict_empty : Test test_dict_empty = let opt = dict "mykey" test s = opt.getOption (opt.set s Dict.empty) |> Expect.equal (Just s) in fuzz string "Common.dict should set and get value by key (empty dict)" test test_dict_list : Test test_dict_list = let opt = (dict "Tom") test s = .getOption opt (Dict.fromList [ ( "Tom", s ), ( "Alice", "Rabbit" ) ]) |> Expect.equal (Just s) in fuzz string "Common.dict should set and get value by key (preloaded dict)" test ================================================ FILE: tests/ComposeSpec.elm ================================================ module ComposeSpec exposing (..) import Array import Expect import Fuzz exposing (constant, float, int, intRange, list, maybe, oneOf, string, tuple, tuple3) import Monocle.Compose as Compose import Monocle.Iso exposing (Iso) import Monocle.Lens exposing (Lens) import Monocle.Optional exposing (Optional) import Monocle.Prism exposing (Prism) import Monocle.Traversal as Traversal import Test exposing (..) all : Test all = describe "Monocle.Compose" [ test_isoWithIso , test_isoWithLens , test_isoWithOptional , test_isoWithPrism , test_isoWithTraversal , test_lensWithIso , test_lensWithLens , test_lensWithOptional , test_lensWithPrism , test_lensWithTraversal , test_prismWithIso , test_prismWithLens , test_prismWithOptional , test_prismWithPrism , test_prismWithTraversal , test_optionalWithIso , test_optionalWithLens , test_optionalWithOptional , test_optionalWithPrism , test_optionalWithTraversal , test_traversalWithIso , test_traversalWithLens , test_traversalWithOptional , test_traversalWithPrism , test_traversalWithTraversal ] test_isoWithIso : Test test_isoWithIso = let isoString2CharList = Iso String.toList String.fromList isoCharList2CharArray = Iso Array.fromList Array.toList isoComposedString2CharArray = isoString2CharList |> Compose.isoWithIso isoCharList2CharArray string2CharArray = String.toList >> Array.fromList test_get s = s |> .get isoComposedString2CharArray |> Expect.equal (s |> string2CharArray) test_reverseGet s = (s |> string2CharArray) |> .reverseGet isoComposedString2CharArray |> Expect.equal s in describe "Compose.isoWithIso" [ test_get |> fuzz string ".get" , test_reverseGet |> fuzz string ".reverseGet" ] type Admin name = Admin name type User name = User name test_isoWithLens : Test test_isoWithLens = let isoUser2Admin = Iso (\(User name) -> Admin name) (\(Admin name) -> User name) lensAdminName = Lens (\(Admin name) -> name) (\name admin -> Admin name) lensComposedUser2AdminName = isoUser2Admin |> Compose.isoWithLens lensAdminName test_get name = User name |> .get lensComposedUser2AdminName |> Expect.equal name test_set ( oldName, newName ) = User oldName |> .set lensComposedUser2AdminName newName |> Expect.equal (User newName) in describe "Compose.isoWithLens" [ test_get |> fuzz string ".get" , test_set |> fuzz (tuple ( string, string )) ".set" ] test_isoWithOptional : Test test_isoWithOptional = let isoUser2Admin = Iso (\(User name) -> Admin name) (\(Admin name) -> User name) optionalAdminName = Optional (\(Admin name) -> name) (\name admin -> Admin (Just name)) optionalComposedUser2AdminName = isoUser2Admin |> Compose.isoWithOptional optionalAdminName test_getOption name = User (Just name) |> .getOption optionalComposedUser2AdminName |> Expect.equal (Just name) test_set ( oldName, newName ) = User (Just oldName) |> .set optionalComposedUser2AdminName newName |> Expect.equal (User (Just newName)) in describe "Compose.isoWithOptional" [ test_getOption |> fuzz string ".getOption" , test_set |> fuzz (tuple ( string, string )) ".set" ] test_isoWithPrism : Test test_isoWithPrism = let isoCharList2String = Iso String.fromList String.toList prismString2Int = Prism String.toInt String.fromInt int2CharList = String.fromInt >> String.toList prismComposedCharList2Int = isoCharList2String |> Compose.isoWithPrism prismString2Int test_getOption int = (int |> int2CharList) |> .getOption prismComposedCharList2Int |> Expect.equal (Just int) test_reverseGet int = int |> .reverseGet prismComposedCharList2Int |> Expect.equal (int |> int2CharList) in describe "Compose.isoWithPrism" [ test_getOption |> fuzz int ".getOption" , test_reverseGet |> fuzz int ".reverseGet" ] test_isoWithTraversal : Test test_isoWithTraversal = let isoString2CharList = Iso String.toList String.fromList traversalStringChars = isoString2CharList |> Compose.isoWithTraversal Traversal.list shift = Char.toCode >> (+) 1 >> Char.fromCode test_modify string = let modified = string |> Traversal.modify traversalStringChars shift expected = string |> String.toList |> List.map shift |> String.fromList in Expect.equal modified expected in describe "Compose.isoWithTraversal" [ test_modify |> fuzz string "modify" ] test_lensWithIso : Test test_lensWithIso = let lensUser2Name = Lens .name (\name user -> { user | name = name }) isoReverse = Iso String.reverse String.reverse lensUser2NameReverse = lensUser2Name |> Compose.lensWithIso isoReverse test_get name = { name = name } |> .get lensUser2NameReverse |> Expect.equal (name |> String.reverse) test_set ( oldName, newName ) = { name = oldName } |> .set lensUser2NameReverse newName |> Expect.equal { name = newName |> String.reverse } in describe "Compose.lensWithIso" [ test_get |> fuzz string ".get" , test_set |> fuzz (tuple ( string, string )) ".set" ] test_lensWithLens : Test test_lensWithLens = let lensPerson2StreetAddress = Lens .streetAddress (\streetAddress person -> { person | streetAddress = streetAddress }) lensStreetAddress2City = Lens .city (\city streetAddress -> { streetAddress | city = city }) lensPerson2StreetAddressCity = lensPerson2StreetAddress |> Compose.lensWithLens lensStreetAddress2City personify ( name, street, city ) = { name = name , streetAddress = { street = street , city = city } } test_get ( name, street, city ) = personify ( name, street, city ) |> .get lensPerson2StreetAddressCity |> Expect.equal city test_set ( ( name, street, city ), newCity ) = personify ( name, street, city ) |> .set lensPerson2StreetAddressCity newCity |> Expect.equal (personify ( name, street, newCity )) in describe "Compose.lensWithLens" [ test_get |> fuzz (tuple3 ( string, string, string )) ".get" , test_set |> fuzz (tuple ( tuple3 ( string, string, string ), string )) ".set" ] test_lensWithOptional : Test test_lensWithOptional = let lensDriver2Vehicle = Lens .vehicle (\vehicle driver -> { driver | vehicle = vehicle }) optionalVehicle2LicenseNumber = Optional .licenseNumber (\licenseNumber vehicle -> { vehicle | licenseNumber = Just licenseNumber }) optionalDriver2LicenseNumber = lensDriver2Vehicle |> Compose.lensWithOptional optionalVehicle2LicenseNumber vehiclify ( name, serie, licenseNumber ) = { name = name , vehicle = { serie = serie , licenseNumber = Just licenseNumber } } test_getOption ( name, serie, licenseNumber ) = vehiclify ( name, serie, licenseNumber ) |> .getOption optionalDriver2LicenseNumber |> Expect.equal (Just licenseNumber) test_set ( ( name, serie, licenseNumber ), newLicenseNumber ) = vehiclify ( name, serie, licenseNumber ) |> .set optionalDriver2LicenseNumber newLicenseNumber |> Expect.equal (vehiclify ( name, serie, newLicenseNumber )) in describe "Compose.lensWithOptional" [ test_getOption |> fuzz (tuple3 ( string, string, string )) ".getOption" , test_set |> fuzz (tuple ( tuple3 ( string, string, string ), string )) ".set" ] test_lensWithPrism : Test test_lensWithPrism = let lensPerson2FirstName = Lens .firstName (\firstName person -> { person | firstName = firstName }) prismName2Number = Prism String.toInt String.fromInt optionalPerson2Number = lensPerson2FirstName |> Compose.lensWithPrism prismName2Number personify ( firstName, lastName ) = { firstName = firstName , lastName = lastName } test_getOption ( firstName, lastName ) = personify ( firstName |> String.fromInt, lastName ) |> .getOption optionalPerson2Number |> Expect.equal (Just firstName) test_set ( ( firstName, lastName ), newFirstName ) = personify ( firstName, lastName ) |> .set optionalPerson2Number newFirstName |> Expect.equal (personify ( newFirstName |> String.fromInt, lastName )) in describe "Compose.lensWithPrism" [ test_getOption |> fuzz (tuple ( int, string )) ".getOption" , test_set |> fuzz (tuple ( tuple ( string, string ), int )) ".set" ] test_lensWithTraversal : Test test_lensWithTraversal = let lensPerson2Friends = Lens .friends (\friends person -> { person | friends = friends }) traversalPersonFriends = lensPerson2Friends |> Compose.lensWithTraversal Traversal.list personify name friends = { name = name , friends = friends } test_modify ( name, friends ) = personify name friends |> Traversal.modify traversalPersonFriends String.reverse |> Expect.equal (personify name (List.map String.reverse friends)) in describe "Compose.lensWithTraversal" [ test_modify |> fuzz (tuple ( int, list string )) ".modify" ] test_prismWithIso : Test test_prismWithIso = let prismString2Int = Prism String.toInt String.fromInt isoPlusOne = Iso (\n -> n + 1) (\n -> n - 1) prismIntPlusOne = prismString2Int |> Compose.prismWithIso isoPlusOne test_getOption quantity = (quantity |> String.fromInt) |> .getOption prismIntPlusOne |> Expect.equal (Just (quantity + 1)) in describe "Compose.prismWithIso" [ test_getOption |> fuzz int ".getOption" ] type alias Address = { city : String , street : Maybe String } type alias Coordinates = { lng : Float , lat : Float } type Location = ByAddress Address | ByCoordinates Coordinates test_prismWithLens : Test test_prismWithLens = let coordinates location = case location of ByCoordinates c -> Just c _ -> Nothing prismLocationToCoordinates = Prism coordinates ByCoordinates lensLng = Lens .lng (\lng c -> { c | lng = lng }) optionalLocationLng = prismLocationToCoordinates |> Compose.prismWithLens lensLng test_getOption ( lng, lat ) = ByCoordinates { lng = lng, lat = lat } |> .getOption optionalLocationLng |> Expect.equal (Just lng) test_set ( ( lng, lat ), newLng ) = ByCoordinates { lng = lng, lat = lat } |> .set optionalLocationLng newLng |> Expect.equal (ByCoordinates { lng = newLng, lat = lat }) in describe "Compose.prismWithLens" [ test_getOption |> fuzz (tuple ( float, float )) ".getOption" , test_set |> fuzz (tuple ( tuple ( float, float ), float )) ".set" ] test_prismWithOptional : Test test_prismWithOptional = let address location = case location of ByAddress a -> Just a _ -> Nothing prismLocationToAddress = Prism address ByAddress optionalStreet = Optional .street (\street a -> { a | street = Just street }) optionalLocationStreet = prismLocationToAddress |> Compose.prismWithOptional optionalStreet test_getOption ( street, city ) = ByAddress { street = Just street, city = city } |> .getOption optionalLocationStreet |> Expect.equal (Just street) test_set ( ( street, city ), newStreet ) = ByAddress { street = Just street, city = city } |> .set optionalLocationStreet newStreet |> Expect.equal (ByAddress { street = Just newStreet, city = city }) in describe "Compose.prismWithOptional" [ test_getOption |> fuzz (tuple ( string, string )) ".getOption" , test_set |> fuzz (tuple ( tuple ( string, string ), string )) ".set" ] type Option = OptionOne | OptionTwo | OptionThree test_prismWithPrism : Test test_prismWithPrism = let toOption n = case n of 1 -> Just OptionOne 2 -> Just OptionTwo 3 -> Just OptionThree _ -> Nothing fromOption option = case option of OptionOne -> 1 OptionTwo -> 2 OptionThree -> 3 anyOption = [ OptionOne , OptionTwo , OptionThree ] |> List.map constant prismString2Int = Prism String.toInt String.fromInt prismInt2Option = Prism toOption fromOption prismString2Option = prismString2Int |> Compose.prismWithPrism prismInt2Option test_getOption n = (n |> String.fromInt) |> .getOption prismString2Option |> Expect.equal (n |> toOption) test_reverseGet option = option |> .reverseGet prismString2Option |> Expect.equal (option |> fromOption |> String.fromInt) in describe "Compose.prismWithPrism" [ test_getOption |> fuzz (intRange 1 3) ".getOption" , test_reverseGet |> fuzz (oneOf anyOption) ".reverseGet" ] type PseudoList a = None | One a | Two a a test_prismWithTraversal : Test test_prismWithTraversal = let toShortList items = case items of [] -> Just [] [ x ] -> Just [ x ] [ x, y ] -> Just [ x, y ] _ -> Nothing fromShortList items = items prismShortList = Prism toShortList fromShortList traverseShortList = prismShortList |> Compose.prismWithTraversal Traversal.list test_modify_succeeds items = items |> Traversal.modify traverseShortList ((+) 1) |> Expect.equal (items |> List.map ((+) 1)) test_modify_fails items = items |> Traversal.modify traverseShortList ((+) 1) |> Expect.equal items listOfUpToTwoInts = Fuzz.map2 (\maybeX maybeY -> List.filterMap identity [ maybeX, maybeY ]) (maybe int) (maybe int) listOfThreeOrMoreInts = Fuzz.map4 (\x y z more -> x :: y :: z :: more) int int int (list int) in describe "Compose.prismWithTraversal" [ test_modify_succeeds |> fuzz listOfUpToTwoInts "modify succeeds" , test_modify_fails |> fuzz listOfThreeOrMoreInts "modify fails" ] test_optionalWithIso : Test test_optionalWithIso = let optionalCity = Optional .city (\city address -> { address | city = Just city }) isoReverse = Iso String.reverse String.reverse optionalCityReverse = optionalCity |> Compose.optionalWithIso isoReverse test_getOption city = { city = Just city } |> .getOption optionalCityReverse |> Expect.equal (Just (city |> String.reverse)) test_set ( city, newCity ) = { city = Just city } |> .set optionalCityReverse (newCity |> String.reverse) |> Expect.equal { city = Just newCity } in describe "Compose.optionalWithIso" [ test_getOption |> fuzz string ".getOption" , test_set |> fuzz (tuple ( string, string )) ".set" ] test_optionalWithLens : Test test_optionalWithLens = let optionalAddress = Optional .address (\address person -> { person | address = Just address }) lensStreet = Lens .street (\street address -> { address | street = street }) optionalAddressStreet = optionalAddress |> Compose.optionalWithLens lensStreet personify ( name, city, street ) = { name = name , address = Just { city = city , street = street } } test_getOption ( name, city, street ) = personify ( name, city, street ) |> .getOption optionalAddressStreet |> Expect.equal (Just street) test_set ( ( name, city, street ), newStreet ) = personify ( name, city, street ) |> .set optionalAddressStreet newStreet |> Expect.equal (personify ( name, city, newStreet )) in describe "Compose.optionalWithLens" [ test_getOption |> fuzz (tuple3 ( string, string, string )) ".getOption" , test_set |> fuzz (tuple ( tuple3 ( string, string, string ), string )) ".set" ] test_optionalWithOptional : Test test_optionalWithOptional = let optionalAddress = Optional .address (\address person -> { person | address = Just address }) optionalStreet = Optional .street (\street address -> { address | street = Just street }) optionalAddressStreet = optionalAddress |> Compose.optionalWithOptional optionalStreet personify ( name, city, street ) = { name = name , address = Just { city = city , street = Just street } } test_getOption ( name, city, street ) = personify ( name, city, street ) |> .getOption optionalAddressStreet |> Expect.equal (Just street) test_set ( ( name, city, street ), newStreet ) = personify ( name, city, street ) |> .set optionalAddressStreet newStreet |> Expect.equal (personify ( name, city, newStreet )) in describe "Compose.optionalWithOptional" [ test_getOption |> fuzz (tuple3 ( string, string, string )) ".getOption" , test_set |> fuzz (tuple ( tuple3 ( string, string, string ), string )) ".set" ] test_optionalWithPrism : Test test_optionalWithPrism = let optionalZipCode = Optional .zipcode (\zipcode address -> { address | zipcode = Just zipcode }) prismString2Int = Prism String.toInt String.fromInt optionalZipCodeString2Int = optionalZipCode |> Compose.optionalWithPrism prismString2Int test_getOption zipcode = { zipcode = Just (zipcode |> String.fromInt) } |> .getOption optionalZipCodeString2Int |> Expect.equal (Just zipcode) test_set ( zipcode, newZipCode ) = { zipcode = Just (zipcode |> String.fromInt) } |> .set optionalZipCodeString2Int newZipCode |> Expect.equal { zipcode = Just (newZipCode |> String.fromInt) } in describe "Compose.optionalWithPrism" [ test_getOption |> fuzz int ".getOption" , test_set |> fuzz (tuple ( int, int )) ".set" ] test_optionalWithTraversal : Test test_optionalWithTraversal = let optionalFriends = Optional .friends (\friends person -> { person | friends = Just friends }) traversalFriends = optionalFriends |> Compose.optionalWithTraversal Traversal.list test_modify_succeeds friends = { friends = Just friends } |> Traversal.modify traversalFriends String.reverse |> Expect.equal { friends = Just (List.map String.reverse friends) } test_modify_fails _ = { friends = Nothing } |> Traversal.modify traversalFriends String.reverse |> Expect.equal { friends = Nothing } in describe "Compose.optionalWithTraversal" [ test_modify_succeeds |> fuzz (list string) "modify succeeds" , test_modify_fails |> test "modify fails" ] test_traversalWithIso : Test test_traversalWithIso = let isoString2Chars = Iso String.toList String.fromList traverseStringsChars = Traversal.list |> Compose.traversalWithIso isoString2Chars test_modify_all words = words |> Traversal.modify traverseStringsChars List.reverse |> Expect.equal (List.map String.reverse words) in describe "Compose.traversalWithIso" [ test_modify_all |> fuzz (list string) "modify" ] test_traversalWithLens : Test test_traversalWithLens = let lensPersonFirstName = Lens .firstName (\firstName person -> { person | firstName = firstName }) traversePeopleFirstNames = Traversal.list |> Compose.traversalWithLens lensPersonFirstName test_modify_all people = people |> Traversal.modify traversePeopleFirstNames String.reverse |> Expect.equal (List.map (\person -> { person | firstName = String.reverse person.firstName }) people) randomPerson = Fuzz.map2 (\firstName lastName -> { firstName = firstName, lastName = lastName }) string string in describe "Compose.traversalWithLens" [ test_modify_all |> fuzz (list randomPerson) "modify" ] test_traversalWithOptional : Test test_traversalWithOptional = let optionalPersonAddress = Optional .address (\address person -> { person | address = Just address }) traversePeopleAddresses = Traversal.list |> Compose.traversalWithOptional optionalPersonAddress test_modify_all people = people |> Traversal.modify traversePeopleAddresses String.reverse |> Expect.equal (List.map (\person -> { person | address = Maybe.map String.reverse person.address }) people) randomPerson = Fuzz.map2 (\name address -> { name = name, address = address }) string (maybe string) in describe "Compose.traversalWithOptional" [ test_modify_all |> fuzz (list randomPerson) "modify" ] test_traversalWithPrism : Test test_traversalWithPrism = let prismString2Int = Prism String.toInt String.fromInt traverseStringInts = Traversal.list |> Compose.traversalWithPrism prismString2Int test_modify_all ints = ints |> List.map String.fromInt |> Traversal.modify traverseStringInts ((+) 1) |> Expect.equal (List.map ((+) 1 >> String.fromInt) ints) test_modify_some intValue = [ "NaN", String.fromInt intValue ] |> Traversal.modify traverseStringInts ((+) 1) |> Expect.equal [ "NaN", String.fromInt (intValue + 1) ] in describe "Compose.traversalWithPrism" [ test_modify_all |> fuzz (list int) "modify all" , test_modify_some |> fuzz int "modify some" ] test_traversalWithTraversal : Test test_traversalWithTraversal = let traverseListOfList = Traversal.list |> Compose.traversalWithTraversal Traversal.list test_modify_all listOfLists = listOfLists |> Traversal.modify traverseListOfList ((+) 1) |> Expect.equal (List.map (List.map ((+) 1)) listOfLists) in describe "Compose.traversalWithTraversal" [ test_modify_all |> fuzz (list (list int)) "modify" ] ================================================ FILE: tests/IsoSpec.elm ================================================ module IsoSpec exposing (..) import Test exposing (..) import Expect import Fuzz exposing (list, int, tuple, string, char) import String import Monocle.Iso exposing (Iso) all : Test all = describe "An Iso specification" [ test_iso_function_get , test_iso_function_reverse_get , test_iso_property_identity , test_iso_property_identity_reversed , test_iso_method_reverse , test_iso_method_modify , test_iso_method_compose ] string2CharListIso : Iso String (List Char) string2CharListIso = Iso String.toList String.fromList charList2StringIso : Iso (List Char) String charList2StringIso = Iso String.fromList String.toList test_iso_function_get : Test test_iso_function_get = let iso = string2CharListIso test s = s |> iso.get |> Expect.equal (String.toList s) in fuzz string "test get function" test test_iso_function_reverse_get : Test test_iso_function_reverse_get = let iso = string2CharListIso test s = (String.toList s) |> iso.reverseGet |> Expect.equal s in fuzz string "test reverseGet function" test test_iso_property_identity : Test test_iso_property_identity = let iso = string2CharListIso test s = s |> iso.get >> iso.reverseGet |> Expect.equal s in fuzz string "test identity property: reverseGet(get(x)) == x" test test_iso_property_identity_reversed : Test test_iso_property_identity_reversed = let iso = charList2StringIso test s = s |> iso.reverseGet >> iso.get |> Expect.equal s in fuzz string "test identity property reversed: get(reverseGet(x)) == x" test test_iso_method_reverse : Test test_iso_method_reverse = let iso = string2CharListIso isor = Monocle.Iso.reverse iso test s = Expect.equal (iso.get s) (isor.reverseGet s) in fuzz string "test reverse method" test test_iso_method_modify : Test test_iso_method_modify = let iso = string2CharListIso test s ch = let fx xs = ch :: xs modified = Monocle.Iso.modify iso fx in modified s |> Expect.equal (String.cons ch s) in fuzz2 string char "test modify method" test test_iso_method_compose : Test test_iso_method_compose = let iso = Monocle.Iso.compose string2CharListIso charList2StringIso test s = s |> iso.get |> Expect.equal s in fuzz string "test compose method" test ================================================ FILE: tests/LensSpec.elm ================================================ module LensSpec exposing (..) import Test exposing (..) import Expect import Fuzz exposing (Fuzzer, list, int, string, char) import String import Monocle.Iso exposing (Iso) import Monocle.Prism exposing (Prism) import Monocle.Lens exposing (Lens, compose, modify, modify2, modify3, zip, modifyAndMerge, tuple, tuple3) import Maybe exposing (Maybe) all : Test all = describe "A Lens specification" [ test_lens_property_identity , test_lens_property_identity_reverse , test_lens_method_compose , test_lens_method_modify , test_lens_method_modify2 , test_lens_method_modify3 , test_lens_method_zip , test_lens_method_modifyAndMerge , test_lens_method_tuple , test_lens_method_tuple3 ] 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 : String , address : Address } addressStreetNameLens : Lens Address String addressStreetNameLens = let get a = a.streetName set sn a = { a | streetName = sn } in Lens get set addressPostcodeLens : Lens Address String addressPostcodeLens = let get a = a.postcode set p a = { a | postcode = p } in Lens get set addressTownLens : Lens Address String addressTownLens = let get a = a.town set t a = { a | town = t } in Lens get set placeAddressLens : Lens Place Address placeAddressLens = let get p = p.address set a p = { p | address = a } in Lens get set addresses : Fuzzer Address addresses = let address name town postcode = { streetName = name, streetType = Street, floor = Nothing, town = town, region = Nothing, postcode = postcode, country = US } in Fuzz.map3 address string string string places : Fuzzer Place places = Fuzz.map3 Place string string addresses test_lens_property_identity : Test test_lens_property_identity = let lens = addressStreetNameLens test address = lens.set (lens.get address) address |> Expect.equal address in fuzz addresses "For all a: A, (set (get a) a) == a" test test_lens_property_identity_reverse : Test test_lens_property_identity_reverse = let lens = addressStreetNameLens test ( street, address ) = lens.get (lens.set street address) |> Expect.equal street in fuzz (Fuzz.tuple ( string, addresses )) "For all a: A, get (set a a) == a" test test_lens_method_compose : Test test_lens_method_compose = let lens = compose placeAddressLens addressStreetNameLens test ( street, place ) = lens.get (lens.set street place) |> Expect.equal street in fuzz (Fuzz.tuple ( string, places )) "Lens compose method" test test_lens_method_modify : Test test_lens_method_modify = let fx street = String.reverse street lens = compose placeAddressLens addressStreetNameLens expected place = lens.set (String.reverse (lens.get place)) place test place = place |> (modify lens fx) |> Expect.equal (expected place) in fuzz places "Lens modify method" test test_lens_method_modify2 : Test test_lens_method_modify2 = let fx ( street, postcode ) = ( String.reverse street, String.append "_" postcode ) lens1 = compose placeAddressLens addressStreetNameLens lens2 = addressPostcodeLens lens3 = addressPostcodeLens expected ( place, address ) = ( place |> lens1.set (String.reverse (lens1.get place)) , address |> lens2.set (String.append "_" (lens2.get address)) ) test x = x |> (modify2 lens1 lens2 fx) |> Expect.equal (expected x) in fuzz (Fuzz.tuple ( places, addresses )) "Lens modify2 method" test test_lens_method_modify3 : Test test_lens_method_modify3 = let fx ( street1, street2, street3 ) = ( street3, street1, street2 ) lens = compose placeAddressLens addressStreetNameLens expected ( place1, place2, place3 ) = ( place1 |> lens.set (lens.get place3) , place2 |> lens.set (lens.get place1) , place3 |> lens.set (lens.get place2) ) test x = x |> (modify3 lens lens lens fx) |> Expect.equal (expected x) in fuzz (Fuzz.tuple3 ( places, places, places )) "Lens modify3 method" test test_lens_method_zip : Test test_lens_method_zip = let address = Address "test" Street Nothing "test" Nothing "test" US place = Place "test" "test" address lens = zip placeAddressLens addressStreetNameLens test x = lens.get (lens.set x ( place, address )) |> Expect.equal x in fuzz (Fuzz.tuple ( addresses, string )) "Lens zip method" test test_lens_method_modifyAndMerge : Test test_lens_method_modifyAndMerge = let lens = compose placeAddressLens addressStreetNameLens fx a = ( String.reverse a, String.length a ) merge a b = a + b modifiedFx = modifyAndMerge lens fx merge expected ( place, n ) = ( (lens.set (String.reverse (lens.get place)) place), n + (String.length (lens.get place)) ) test p = modifiedFx p |> Expect.equal (expected p) in fuzz (Fuzz.tuple ( places, int )) "Lens modifyAndMerge method" test test_lens_method_tuple : Test test_lens_method_tuple = let lens = tuple addressStreetNameLens addressPostcodeLens test ( address, street, postcode ) = lens.get (lens.set ( street, postcode ) address) |> Expect.equal ( street, postcode ) in fuzz (Fuzz.tuple3 ( addresses, string, string )) "Lens tuple method" test test_lens_method_tuple3 : Test test_lens_method_tuple3 = let lens = tuple3 addressStreetNameLens addressPostcodeLens addressTownLens test ( address, ( street, postcode, town ) ) = lens.get (lens.set ( street, postcode, town ) address) |> Expect.equal ( street, postcode, town ) in fuzz (Fuzz.tuple ( addresses, Fuzz.tuple3 ( string, string, string ) )) "Lens tuple3 method" test ================================================ FILE: tests/OptionalSpec.elm ================================================ module OptionalSpec exposing (..) import Test exposing (..) import Expect import Fuzz exposing (Fuzzer, list, int, tuple, string, char) import String import Monocle.Iso exposing (Iso) import Monocle.Prism exposing (Prism) import Monocle.Lens exposing (Lens, fromIso) import Monocle.Optional exposing (Optional, fromPrism, fromLens, compose, composeLens, modifyOption, modify, zip) import Maybe exposing (Maybe) all : Test all = describe "An Optional specification" [ test_optional_property_identity_when_just , test_optional_property_identity_when_nothing , test_optional_property_reverse_identity , test_optional_method_fromPrism_getOption , test_optional_method_fromPrism_set , test_optional_method_compose , test_optional_method_composeLens , test_lens_method_modifyOption_just , test_lens_method_modify_just , test_lens_method_modifyOption_nothing , test_optional_method_zip , test_optional_method_fromLens ] 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 : String , address : Maybe Address } addressRegionOptional : Optional Address String addressRegionOptional = let getOption a = a.region set r a = { a | region = Just r } in Optional getOption set addressStreetNameLens : Lens Address String addressStreetNameLens = let get a = a.streetName set sn a = { a | streetName = sn } in Lens get set placeAddressOptional : Optional Place Address placeAddressOptional = let getOption p = p.address set a p = { p | address = Just a } in Optional getOption set string2IntPrism : Prism String Int string2IntPrism = Prism String.toInt String.fromInt string2CharListIso : Iso String (List Char) string2CharListIso = Iso String.toList String.fromList addressesWithRegion : Fuzzer Address addressesWithRegion = let address name town postcode region = { streetName = name, streetType = Street, floor = Nothing, town = town, region = Just region, postcode = postcode, country = US } in Fuzz.map4 address string string string string addressesWithoutRegion : Fuzzer Address addressesWithoutRegion = let address name town postcode = { streetName = name, streetType = Street, floor = Nothing, town = town, region = Nothing, postcode = postcode, country = US } in Fuzz.map3 address string string string places : Fuzzer Place places = Fuzz.map3 Place string string (Fuzz.maybe addressesWithRegion) numbers : Fuzzer String numbers = Fuzz.map String.fromInt int test_optional_property_identity_when_just = let opt = addressRegionOptional test a = opt.getOption a |> Maybe.map (\r -> opt.set r a) |> Expect.equal (Just a) in fuzz addressesWithRegion "For some a: A, getOption a |> Maybe.map (r -> set r a) == Just a" test test_optional_property_identity_when_nothing = let opt = addressRegionOptional test a = opt.getOption a |> Maybe.map (\r -> opt.set r a) |> Expect.equal Nothing in fuzz addressesWithoutRegion "For some a: A, getOption a |> Maybe.map (r -> set r a) == Nothing" test test_optional_property_reverse_identity = let opt = addressRegionOptional test ( a, r ) = opt.set r a |> opt.getOption |> Expect.equal (Just r) in fuzz (Fuzz.tuple ( addressesWithoutRegion, string )) "For all a: A, set a r |> getOption == Just a" test test_optional_method_fromPrism_getOption = let opt = fromPrism string2IntPrism expected s = Just (String.toInt s |> Maybe.withDefault 0) test s = opt.getOption s |> Expect.equal (expected s) in fuzz numbers "Optional.fromPrism.getOption" test test_optional_method_fromPrism_set = let opt = fromPrism string2IntPrism test i = opt.set i "" |> Expect.equal (String.fromInt i) in fuzz int "Optional.fromPrism.set" test test_optional_method_compose = let opt = compose addressRegionOptional (fromPrism string2IntPrism) computed ( a, i ) = opt.set i a expected ( a, i ) = { a | region = Just (String.fromInt i) } in fuzz (Fuzz.tuple ( addressesWithRegion, int )) "Optional.compose" (\s -> Expect.equal (computed s) (expected s)) test_optional_method_composeLens = let opt = composeLens addressRegionOptional (fromIso string2CharListIso) computed ( a, cl ) = opt.set cl a expected ( a, cl ) = { a | region = Just (String.fromList cl) } in fuzz (Fuzz.tuple ( addressesWithRegion, list char )) "Optional.composeLens" (\s -> Expect.equal (computed s) (expected s)) test_lens_method_modifyOption_just = let f sn = String.reverse sn opt = addressRegionOptional computed a = modifyOption opt f a expected a = opt.getOption a |> Maybe.map String.reverse |> Maybe.map (\b -> opt.set b a) test s = Expect.equal (computed s) (expected s) in fuzz addressesWithRegion "Optional.modifyOption for Just a" test test_lens_method_modifyOption_nothing = let f sn = String.reverse sn opt = addressRegionOptional computed a = modifyOption opt f a expected a = opt.getOption a |> Maybe.map String.reverse |> Maybe.map (\b -> opt.set b a) test s = Expect.equal (computed s) (expected s) in fuzz addressesWithoutRegion "Optional.modifyOption for Nothing" test test_lens_method_modify_just = let f sn = String.reverse sn opt = addressRegionOptional computed a = modify opt f a expected a = opt.getOption a |> Maybe.map String.reverse |> Maybe.map (\b -> opt.set b a) |> Maybe.withDefault a test s = Expect.equal (computed s) (expected s) in fuzz addressesWithRegion "Optional.modify for Just a" test test_optional_method_zip = let address1 = Address "test" Street Nothing "test" Nothing "test" US address2 = Address "test" Street Nothing "test" (Just "test") "test" US opt = zip addressRegionOptional addressRegionOptional computed x = opt.getOption (opt.set x ( address1, address2 )) expected x = Just x test s = Expect.equal (computed s) (expected s) in fuzz (Fuzz.tuple ( string, string )) "Optional.zip" test test_optional_method_fromLens = let opt = fromLens addressStreetNameLens computed ( a, s ) = opt.set s a expected ( a, s ) = { a | streetName = s } test s = Expect.equal (computed s) (expected s) in fuzz (Fuzz.tuple ( addressesWithRegion, string )) "Optional.fromLens" test ================================================ FILE: tests/PrismSpec.elm ================================================ module PrismSpec exposing (..) import Test exposing (..) import Expect import Fuzz exposing (Fuzzer, list, int, tuple, string, char) import String import Monocle.Iso exposing (Iso) import Monocle.Prism exposing (Prism) all : Test all = describe "A Prism specification" [ test_prism_property_partial_round_trip_one_way , test_prism_property_no_round_trip_when_not_matching , test_prism_property_round_trip_other_way , test_prism_method_modify , test_prism_method_modify_option , test_prism_method_compose , test_prism_method_composeIso , test_prism_method_fromIso , test_prism_method_fromIso_reverseGet ] string2IntPrism : Prism String Int string2IntPrism = Prism String.toInt String.fromInt string2FloatPrism : Prism String Float string2FloatPrism = Prism String.toFloat String.fromFloat float2IntPrism : Prism Float Int float2IntPrism = let getOption float = let int = truncate float back = toFloat int in if (float == back) then Just int else Nothing in Prism getOption toFloat numbers : Fuzzer String numbers = Fuzz.map String.fromInt int notnumbers : Fuzzer String notnumbers = Fuzz.map (\s -> String.append "_" s) string numbersAndNotNumbers : Fuzzer String numbersAndNotNumbers = Fuzz.frequency [ ( 0.5, numbers ), ( 0.5, notnumbers ) ] test_prism_property_partial_round_trip_one_way : Test test_prism_property_partial_round_trip_one_way = let prism = string2IntPrism test s = s |> prism.getOption |> Maybe.map prism.reverseGet |> Expect.equal (Just s) in fuzz numbers "For some a: A, getOption a |> Maybe.map reverseGet == Just a" test test_prism_property_no_round_trip_when_not_matching : Test test_prism_property_no_round_trip_when_not_matching = let prism = string2IntPrism test s = s |> prism.getOption |> Maybe.map prism.reverseGet |> Expect.equal Nothing in fuzz notnumbers "For some a: A, getOption a |> Maybe.map reverseGet == Nothing " test test_prism_property_round_trip_other_way : Test test_prism_property_round_trip_other_way = let prism = string2IntPrism test i = i |> prism.reverseGet >> prism.getOption |> Expect.equal (Just i) in fuzz int "For all a: A, getOption (reverseGet a) == Just a " test test_prism_method_modify : Test test_prism_method_modify = let prism = string2IntPrism computed s = let fx i = i * 2 modified = Monocle.Prism.modify prism fx in modified s expected s = s |> String.toInt >> Maybe.map ((*) 2 >> String.fromInt) |> Maybe.withDefault s test s = Expect.equal (computed s) (expected s) in fuzz string "Prism method modify" test test_prism_method_modify_option : Test test_prism_method_modify_option = let prism = string2IntPrism computed s = let fx i = i * 2 modified = Monocle.Prism.modifyOption prism fx in modified s expected s = s |> String.toInt >> Maybe.map ((*) 2 >> String.fromInt) test s = Expect.equal (computed s) (expected s) in fuzz numbersAndNotNumbers "Prism method modifyOption" test test_prism_method_compose : Test test_prism_method_compose = let iso = Iso ((*) 10) ((//) 10) prism = Monocle.Prism.composeIso string2IntPrism iso computed s = prism.getOption s expected s = s |> String.toInt >> Maybe.map ((*) 10) test s = Expect.equal (computed s) (expected s) in fuzz numbersAndNotNumbers "Prism method compose" test test_prism_method_composeIso : Test test_prism_method_composeIso = let iso = Iso ((*) 10) ((//) 10) prism = Monocle.Prism.composeIso string2IntPrism iso computed s = prism.getOption s expected s = s |> String.toInt >> Maybe.map ((*) 10) test s = Expect.equal (computed s) (expected s) in fuzz numbersAndNotNumbers "Prism method composeIso" test test_prism_method_fromIso : Test test_prism_method_fromIso = let iso = Iso String.toList String.fromList prism = Monocle.Prism.fromIso iso computed s = prism.getOption s expected s = iso.get s |> Just test s = Expect.equal (computed s) (expected s) in fuzz string "Prism method fromIso" test test_prism_method_fromIso_reverseGet : Test test_prism_method_fromIso_reverseGet = let iso = Iso String.fromList String.toList prism = Monocle.Prism.fromIso iso computed s = prism.reverseGet s expected s = iso.reverseGet s test s = Expect.equal (computed s) (expected s) in fuzz string "Prism method fromIso when reverseGet" test ================================================ FILE: tests/Tests.elm ================================================ module Tests exposing (..) import Test exposing (..) import Expect import Fuzz exposing (list, int, tuple, string) import String import IsoSpec import PrismSpec import LensSpec import OptionalSpec import CommonSpec all : Test all = describe "Elm Monocle specification" [ IsoSpec.all , PrismSpec.all , LensSpec.all , OptionalSpec.all , CommonSpec.all ] ================================================ FILE: tests/TraversalSpec.elm ================================================ module TraversalSpec exposing (all) import Array import Expect import Fuzz exposing (array, int, list) import Monocle.Traversal as Traversal import Test exposing (..) all : Test all = describe "A Traversal specification" [ test_list_traversal , test_array_traversal , test_some_traversal ] test_list_traversal : Test test_list_traversal = let test s = s |> Traversal.modify Traversal.list ((*) 2) |> Expect.equal (List.map ((*) 2) s) in fuzz (list int) "Traversal modify on a list" test test_array_traversal : Test test_array_traversal = let test s = s |> Traversal.modify Traversal.array ((*) 2) |> Expect.equal (Array.map ((*) 2) s) in fuzz (array int) "Traversal modify on an array" test test_some_traversal : Test test_some_traversal = let odds = Traversal.some Traversal.list (\n -> remainderBy 2 n == 1) test _ = [ 1, 2, 3, 4, 5 ] |> Traversal.modify odds ((+) 3) |> Expect.equal [ 4, 2, 6, 4, 8 ] in Test.test "Traversal modify some elements in an array" test