Skip to content

irresponsible/polylens.ex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Polylens

An elixirified port of Haskell's lenses using multiple-dispatch polymorphism.

Hexdocs

Usage

alias Polylens.Lenses

def sample, do: {1, %{2 => [3, 4]}}

# These lenses address each of the numbers
def one,   do: [Lenses.at_index(0)]
def two,   do: [Lenses.at_index(1), Lenses.key_at(2)]
def three, do: [Lenses.at_index(1), Lenses.at_key(2), Lenses.at_index(0)]
def four,  do: [Lenses.at_index(1), Lenses.at_key(2), Lenses.at_index(1)]

def numbers, do: [four, three, two, one]

def example do

  # Firstly, we can get the values they lens over
  for number <- numbers do
    {:ok, num} = Polylens.get_in(number, sample)
    IO.inspect(num)
  end

  # We can set them all to the same thing. Result: {42, %{42 => [42, 42]}}
  Enum.reduce(numbers, sample, fn lens, data ->
    {:ok, ret} = Polylens.set_in(lens, data, 42)
    ret
  end
  |> IO.inspect()

  # We can modify them all. Result: {2, %{3 => [4, 5]}}
  Enum.reduce(numbers, sample, fn lens, data ->
    {:ok, ret} = Polylens.update_in(lens, data, fn x -> x + 1 end)
    ret
  end
  |> IO.inspect()

end

Installation

If available in Hex, the package can be installed by adding polylens to your list of dependencies in mix.exs:

def deps do
  [
    # ... other deps ...
    {:polylens, "~> 0.1.0"},
  ]
end

Implementing your own lenses

We use protocol_ex to fake multiple dispatch with {lens, data} tuples via the Lens protocol_ex.

To build your own lenses, you will need to add :protocol_ex to your Mix project compilers:

def project do
  [
    ## ... other config ...
    ## Make sure [:protocol_ex] comes after :elixirc !
    compilers: Mix.compilers ++ [:protocol_ex],
  ]
end

You may then implement the Lens protocol_ex. Here is how we implement AtKey for maps:

import ProtocolEx
alias Polylens.Lens

defimpl_ex MapAtKey, {%Polylens.AtKey{},map} when is_map(map), for: Lens do
  def get({%{key: key}, map}) do
    fail = make_ref()
    case Map.get(map, key, fail) do
      ^fail -> {:error, :not_found}
      ret -> {:ok, ret}
    end
  end
  def set({%{key: key}, map}, value), do: {:ok, Map.put(map, key, value)}
end

Note for Haskell/Purescript users

If you're familiar with haskell or purescript lenses, Polylens lenses most closely represent 'at' lenses because owing to the dynamic nature of Elixir, they may fail.

Changelog

  • Version 0.1.0 (2018-09-02) Initial release

Copyright and License

Copyright 2018 James Laver

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

About

Polymorphic Lenses for fun and profit

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages