diff --git a/lenses.ipynb b/lenses.ipynb new file mode 100644 index 0000000..548ab00 --- /dev/null +++ b/lenses.ipynb @@ -0,0 +1,2007 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Each type of **Optic** comes with a set of compatible **actions**.\n", + "\n", + "Lenses have the following concrete gaurantees.\n", + "\n", + "* A Lens focuses (i.e selects) a single piece of data within a larger structure.\n", + "* A Lens must **never fail** to **get** or **modify** that focus.\n", + "\n", + "These constraints unlock a few **actions** we can perform on lenses.\n", + "\n", + "* We can use a lens to **view** the **focus** within a structure.\n", + "* We can use a lens to **set** the **focus** within a structure.\n", + "* We can use a lens to **modify** the **focus** within a structure." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"hello\"" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "{-# LANGUAGE TemplateHaskell #-}\n", + "{-# LANGUAGE FlexibleInstances #-}\n", + "{-# LANGUAGE FlexibleContexts #-}\n", + "{-# LANGUAGE RankNTypes #-}\n", + "{-# LANGUAGE ScopedTypeVariables #-}\n", + "{-# LANGUAGE TypeApplications #-}\n", + "{-# LANGUAGE TypeFamilies #-}\n", + "{-# LANGUAGE InstanceSigs #-}\n", + "\n", + "import Control.Lens\n", + "import Numeric.Lens\n", + "import Data.Bits.Lens\n", + "import Data.Data.Lens\n", + "\n", + "import Control.Applicative\n", + "import Data.Char as C\n", + "import qualified Data.Map as M\n", + "import qualified Data.Set as S\n", + "import qualified Data.Text as T\n", + "import qualified Data.List as L\n", + "\n", + "view (_2 . _1) (42, (\"hello\", False))\n", + "\n", + "-- view is the action\n", + "-- (_2 . _1) is the path\n", + "-- (42, (\"hello\", False)) is the structure\n", + "-- \"hello\" is the focus" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises - Optic Anatomy" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(False,Left \"new\")" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"TESTING ONE two three\"" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"supercalifragilisticexpialidocious\"" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "view (_1 . _2) ((1, 2), 3)\n", + "\n", + "-- view is the action\n", + "-- (_1 . _2) is the path. Focus on the 1st member of tuple and then the 2nd member of 1st member\n", + "-- ((1, 2), 3) is the structure\n", + "-- 2 is the focus\n", + "\n", + "set (_2 . _Left) \"new\" (False, Left \"old\")\n", + "\n", + "-- set is the action\n", + "-- (_2 . _Left) is the path\n", + "-- (False, Left \"Old\") is the structure\n", + "-- \"old\" is the focus\n", + "\n", + "over (taking 2 worded . traversed) C.toUpper \"testing one two three\"\n", + "\n", + "-- over is the action\n", + "-- (taking 2 worded . traversed) is the path\n", + "-- \"testing one two three\"\n", + "-- \"testing one\" is the focus\n", + "\n", + "foldOf (both . each) ([\"super\", \"cali\"],[\"fragilistic\", \"expialidocious\"])\n", + "\n", + "-- foldOf is the action\n", + "-- (both . each) is the path\n", + "-- The rest is the structure" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Lens Actions\n", + "\n", + "### Viewing through lenses" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "_1 :: forall s t a b (f :: * -> *). (Field1 s t a b, Functor f) => (a -> f b) -> s -> f t" + ], + "text/plain": [ + "_1 :: forall s t a b (f :: * -> *). (Field1 s t a b, Functor f) => (a -> f b) -> s -> f t" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Lens' :: * -> * -> *" + ], + "text/plain": [ + "Lens' :: * -> * -> *" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Lens :: * -> * -> * -> * -> *" + ], + "text/plain": [ + "Lens :: * -> * -> * -> * -> *" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "_2 :: forall s t a b (f :: * -> *). (Field2 s t a b, Functor f) => (a -> f b) -> s -> f t" + ], + "text/plain": [ + "_2 :: forall s t a b (f :: * -> *). (Field2 s t a b, Functor f) => (a -> f b) -> s -> f t" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "view :: forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a" + ], + "text/plain": [ + "view :: forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'a'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'b'" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + ":t _1\n", + "\n", + ":k Lens'\n", + ":k Lens\n", + "\n", + "-- _1 :: Lens' (a, b) a\n", + "-- (a, b) --> Type of structure\n", + "-- a --> Focus within it\n", + "\n", + ":t _2\n", + "\n", + ":t view\n", + "\n", + "-- A lens on it's own isn't enough. We need an action to perform on it.\n", + "\n", + "-- view :: Lens' s a -> s -> a\n", + "-- Get the path's focus from within the structure\n", + "\n", + "view _1 ('a', 'b')\n", + "\n", + "view _2 ('a', 'b')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setting through a lens" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "set :: forall s t a b. ASetter s t a b -> b -> s -> t" + ], + "text/plain": [ + "set :: forall s t a b. ASetter s t a b -> b -> s -> t" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "over :: forall s t a b. ASetter s t a b -> (a -> b) -> s -> t" + ], + "text/plain": [ + "over :: forall s t a b. ASetter s t a b -> (a -> b) -> s -> t" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "('x','b')" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(100,2)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "-- set :: Lens' s a -> a -> s -> s\n", + "-- Set a new value at the path’s focus, leaving the rest of the structure unaltered.\n", + "\n", + "-- over :: Lens' s a -> (a -> a) -> s -> s\n", + "-- Modify the focus of a path by running a function over it, altering it in-place and leaving the\n", + "-- rest of the structure unaltered.\n", + "\n", + ":t set\n", + ":t over\n", + "\n", + "set _1 'x' ('a', 'b')\n", + "\n", + "over _1 (*100) (1, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises - Lens Actions" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'c'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "(False,20)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "-- 1. Find structure & focus in the following lens\n", + "\n", + "-- Lens' (Bool, (Int, String)) Int\n", + "\n", + "-- (Bool, (Int, String)) is the structure\n", + "-- Focus is Int, the 1st member of the 2nd member of the complete structure\n", + "\n", + "-- 2. Write the type signature of a Lens with the structure (Char, Int) and the focus Char\n", + "\n", + "-- Lens' (Char, Int) Char\n", + "\n", + "-- 3. Name three actions we can use on a Lens\n", + "\n", + "-- set\n", + "-- view\n", + "-- over\n", + "\n", + "-- 4\n", + "\n", + "view _3 ('a', 'b', 'c')\n", + "\n", + "over _2 (*10) (False, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Lenses and records\n", + "\n", + "* Lenses subsume the accessor pattern in other languages." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "lens :: forall (f :: * -> *) s a b t. Functor f => (s -> a) -> (s -> b -> t) -> (a -> f b) -> s -> f t" + ], + "text/plain": [ + "lens :: forall (f :: * -> *) s a b t. Functor f => (s -> a) -> (s -> b -> t) -> (a -> f b) -> s -> f t" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Lens :: * -> * -> * -> * -> *" + ], + "text/plain": [ + "Lens :: * -> * -> * -> * -> *" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Lens' :: * -> * -> *" + ], + "text/plain": [ + "Lens' :: * -> * -> *" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "-- Just changed the names from the example in the book\n", + "data Name = \n", + " Name { _name :: String\n", + " , _age :: Int\n", + " } deriving (Show)\n", + " \n", + "-- lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b\n", + "-- lens :: (s -> a) -> (s -> a -> s) -> Lens' s a\n", + "\n", + ":t lens\n", + ":k Lens\n", + ":k Lens'\n", + "\n", + "-- Whenever you see 's' in a Lens thing 'structure' and when you see 'a', think 'focus'.\n", + "\n", + "-- getAge :: s -> a\n", + "getAge :: Name -> Int\n", + "getAge = _age\n", + "\n", + "-- setAge :: s -> a -> s\n", + "setAge :: Name -> Int -> Name\n", + "setAge name newAge = name { _age = newAge }\n", + "\n", + "numAge :: Lens' Name Int\n", + "numAge = lens getAge setAge" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises - Records Part One" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "31" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Name {_name = \"Sanchayan Maity\", _age = 32}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Name {_name = \"Sanchayan Maity\", _age = 34}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Name {_name = \"Sanchayan Maity\", _age = 34}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Sanchayan Maity\"" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Name {_name = \"Foo\", _age = 31}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Name {_name = \"Boo Sanchayan Maity\", _age = 31}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "-- 1. The structure and focus of a lens are typically represented by which letters in type signature\n", + "-- s and a respectively\n", + "\n", + "-- 2. Which two components are required to create a lens\n", + "-- getter and setter\n", + "\n", + "-- Implement the following Lens\n", + "\n", + "-- name :: Lens' Name String\n", + "\n", + "getName :: Name -> String\n", + "getName = _name\n", + "\n", + "setName :: Name -> String -> Name\n", + "setName name newName = name { _name = newName }\n", + "\n", + "name :: Lens' Name String\n", + "name = lens getName setName\n", + "\n", + "myName :: Name\n", + "myName = Name { _name = \"Sanchayan Maity\"\n", + " , _age = 31 } \n", + " \n", + "-- Getting, setting & modifying with a field lens\n", + "\n", + "-- For Age field\n", + "view numAge myName\n", + "set numAge 32 myName\n", + "over numAge (+3) myName\n", + "set numAge (view numAge myName + 3) myName\n", + "\n", + "-- For Name field\n", + "view name myName\n", + "set name \"Foo\" myName\n", + "over name (\"Boo \" ++) myName" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Automatically generating field names" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "name :: forall (f :: * -> *). Functor f => (String -> f String) -> Ship -> f Ship" + ], + "text/plain": [ + "name :: forall (f :: * -> *). Functor f => (String -> f String) -> Ship -> f Ship" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "numCrew :: forall (f :: * -> *). Functor f => (Int -> f Int) -> Ship -> f Ship" + ], + "text/plain": [ + "numCrew :: forall (f :: * -> *). Functor f => (Int -> f Int) -> Ship -> f Ship" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "getNumCrew :: Ship -> Int" + ], + "text/plain": [ + "getNumCrew :: Ship -> Int" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "data Ship = \n", + " Ship { _name :: String\n", + " , _numCrew :: Int \n", + " } deriving (Show)\n", + "\n", + "makeLenses ''Ship\n", + "\n", + ":t name\n", + ":t numCrew\n", + ":t getNumCrew" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises - Records Part Two" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [], + "source": [ + "-- data Inventory = \n", + "-- Inventory { _wand :: Wand\n", + "-- , _book :: Book\n", + "-- , _potions :: [Potion] }\n", + "\n", + "-- makeLenses ''Inventory\n", + "\n", + "-- Lenses generated would be\n", + "\n", + "-- wand\n", + "-- book\n", + "-- potions\n", + "-- getWand\n", + "-- getBook\n", + "-- getPotions\n", + "-- setWand\n", + "-- setBook\n", + "-- setPotions\n", + "\n", + "-- gazork :: Functor f => (Spuzz -> f Spuzz) -> Chumble -> f Chumble\n", + "-- gazork :: Lens' Chumble Spuzz\n", + "\n", + "data Pet = Pet \n", + " { _petName :: String\n", + " , _petType :: String\n", + " }\n", + " \n", + "makeLenses ''Pet\n", + "\n", + "getPetName :: Pet -> String\n", + "getPetName = view petName" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Lens is an optic which **always** accesses **exactly** one focus" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Is it a Lens?\n", + "\n", + "* second :: Lens' (a, b, c) b\n", + "\n", + "* inMaybe :: Lens' (Maybe a) a\n", + "\n", + "* left :: Lens' (Either a b) a\n", + "\n", + "* listThird :: Lens' [a] a\n", + "\n", + "* Can you write a lens which focuses the second element of a tuple if the Bool in the first slot is True and focuses the third element if it’s False ? conditional :: Lens' (Bool, a, a) a\n", + "\n", + "* Consider the below data type. Is it possible to have a lens msg :: Lens' Err String" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [], + "source": [ + "data Err = \n", + " ReallyBadError { _msg :: String }\n", + " | ExitCode { _code :: Int }\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Lens Laws\n", + "\n", + "1. You get back what you set\n", + "2. Setting back what you got doesn't do anything\n", + "3. Setting twice is the same as setting once" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Virtual Fields" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "celsius :: forall (f :: * -> *). Functor f => (Float -> f Float) -> Temperature -> f Temperature" + ], + "text/plain": [ + "celsius :: forall (f :: * -> *). Functor f => (Float -> f Float) -> Temperature -> f Temperature" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "7.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Temperature {_location = \"Bangalore\", _celsius = 13.5}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Temperature {_location = \"Bangalore\", _celsius = 17.0}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "44.6" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Temperature {_location = \"Bangalore\", _celsius = 13.5}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Temperature {_location = \"Bangalore\", _celsius = 17.0}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "data Temperature = \n", + " Temperature { _location :: String\n", + " , _celsius :: Float\n", + " } deriving (Show)\n", + " \n", + "makeLenses ''Temperature\n", + "\n", + ":t celsius\n", + "\n", + "let temp = Temperature \"Bangalore\" 7.0\n", + "\n", + "view celsius temp\n", + "set celsius 13.5 temp\n", + "over celsius (+10) temp\n", + "\n", + "celsiusToFahrenheit :: Float -> Float\n", + "celsiusToFahrenheit c = (c * (9/5)) + 32\n", + "\n", + "fahrenheitToCelsius :: Float -> Float\n", + "fahrenheitToCelsius f = (f - 32) * (5/9)\n", + "\n", + "fahrenheit :: Lens' Temperature Float\n", + "fahrenheit = lens getter setter\n", + " where\n", + " getter = celsiusToFahrenheit . view celsius\n", + " setter temp f = set celsius (fahrenheitToCelsius f) temp\n", + " \n", + "view fahrenheit temp\n", + "set fahrenheit 56.3 temp\n", + "over fahrenheit (+18) temp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note: TODO: The exercises on laws & virtual fields" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Haskell", + "language": "haskell", + "name": "haskell" + }, + "language_info": { + "codemirror_mode": "ihaskell", + "file_extension": ".hs", + "name": "haskell", + "pygments_lexer": "Haskell", + "version": "8.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}