2022-08-24 14:52:32 +00:00
<!DOCTYPE html>
< html lang = 'en' >
< head >
< meta charset = 'UTF-8' / >
< meta name = 'viewport' content = 'width=device-width, initial-scale=1' / >
< title >
Lenses – Home
< / title >
< meta property = 'og:description' content = 'Die Idee dahinter ist, dass man Zugriffsabstraktionen über Daten verknüpfen kann. Als einfachen Datenstruktur kann man einen Record mit der entsprechenden Syntax nehmen.' / >
< meta property = 'og:site_name' content = 'Home' / >
< meta property = 'og:image' content / >
< meta property = 'og:type' content = 'website' / >
< meta property = 'og:title' content = 'Lenses' / >
< base href = '/' / >
< link href = 'favicon.svg' rel = 'icon' / >
< script >
window.MathJax = {
startup: {
ready: () => {
MathJax.startup.defaultReady();
}
}
};
< / script >
< script async id = 'MathJax-script' src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js' > < / script >
<!-- mermaid.js --> < script src = 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js' > < / script >
< script >
mermaid.initialize({startOnLoad:false});
mermaid.init(undefined,document.querySelectorAll(".mermaid"));
< / script >
<!-- highlight.js -->
< link rel = 'stylesheet' href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/hybrid.min.css' / >
< script src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js' > < / script >
<!-- Include languages that Emanote itself uses -->
< script src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/languages/haskell.min.js' > < / script >
< script src = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/languages/nix.min.js' > < / script >
< script > hljs . highlightAll ( ) ; < / script >
2022-11-23 11:22:29 +00:00
< link href = 'tailwind.css?instanceId=1ec53b8c-7991-423c-8b17-b92e36792648' rel = 'stylesheet' type = 'text/css' / >
2022-08-24 14:52:32 +00:00
< style >
2022-11-23 11:22:29 +00:00
/* Heist error element */
2022-08-24 14:52:32 +00:00
strong.error {
color: lightcoral;
font-size: 90%;
font-family: monospace;
}
2022-11-23 11:22:29 +00:00
/* External link icon */
a[data-linkicon]:not([data-linkicon=""]):not([data-linkicon="none"])::after {
/* filter converts black to rgb(156,163,175) */
filter: invert(71%) sepia(3%) saturate(904%) hue-rotate(179deg) brightness(92%) contrast(87%);
margin-left: 1px;
}
a[data-linkicon]:not([data-linkicon=""]):not([data-linkicon="none"]):hover::after {
/* filter converts black to rgb(175,85,99) */
filter: invert(32%) sepia(10%) saturate(834%) hue-rotate(176deg) brightness(92%) contrast(88%);
}
a[data-linkicon=""]::after {
content: ""
}
a[data-linkicon=none]::after {
content: ""
}
a[data-linkicon="external"]::after {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='1em' fill='none' viewBox='0 0 24 24' stroke='black' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14' /%3E%3C/svg%3E");
}
a[data-linkicon="external"][href^="mailto:"]::after {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='1em' fill='none' viewBox='0 0 24 24' stroke='black' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z' /%3E%3C/svg%3E");
}
2022-08-24 14:52:32 +00:00
< / style >
2022-11-23 11:22:29 +00:00
<!-- What goes in this file will appear on near the end of <head> --> < link rel = 'preload' href = '_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf' as = 'font' type = 'font/ttf' crossorigin / >
2022-08-24 14:52:32 +00:00
< style >
@font-face {
2022-11-23 11:22:29 +00:00
font-family: 'WorkSans';
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf */
src: url(_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf) format("truetype");
2022-08-24 14:52:32 +00:00
font-display: swap;
}
body {
2022-11-23 11:22:29 +00:00
font-family: 'WorkSans', sans-serif;
font-variation-settings: 'wght' 350;
2022-08-24 14:52:32 +00:00
}
a.mavenLinkBold {
2022-11-23 11:22:29 +00:00
font-variation-settings: 'wght' 400;
2022-08-24 14:52:32 +00:00
}
strong {
2022-11-23 11:22:29 +00:00
font-variation-settings: 'wght' 500;
2022-08-24 14:52:32 +00:00
}
h1,
h2,
h3,
h4,
h5,
h6,
header,
.header-font {
2022-11-23 11:22:29 +00:00
font-family: 'WorkSans', sans-serif;
}
h1 {
font-variation-settings: 'wght' 500;
}
h2 {
font-variation-settings: 'wght' 400;
}
h3 {
font-variation-settings: 'wght' 300;
2022-08-24 14:52:32 +00:00
}
< / style >
2022-11-23 11:22:29 +00:00
2022-08-24 14:52:32 +00:00
< link rel = 'stylesheet' href = '_emanote-static/inverted-tree.css' / >
2022-11-23 11:22:29 +00:00
< link rel = 'stylesheet' href = '_emanote-static/stork/flat.css' / >
2022-08-24 14:52:32 +00:00
<!-- Custom Stork - search styling for Emanote -->
< style >
#stork-search-container {
z-index: 1000;
background-color: rgb(15 23 42/.8);
}
.stork-overflow-hidden-important {
overflow: hidden !important;
}
< / style >
2022-11-23 11:22:29 +00:00
< script src = '_emanote-static/stork/stork.js' > < / script >
2022-08-24 14:52:32 +00:00
< script data-emanote-base-url = '/' >
window.emanote = {};
window.emanote.stork = {
searchShown: false,
toggleSearch: function () {
document.getElementById('stork-search-container').classList.toggle('hidden');
window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important');
if (window.emanote.stork.searchShown) {
document.getElementById('stork-search-input').focus();
}
},
clearSearch: function () {
document.getElementById('stork-search-container').classList.add('hidden');
document.body.classList.remove('stork-overflow-hidden-important');
window.emanote.stork.searchShown = false;
},
init: function () {
const indexName = 'emanote-search'; // used to match input[data-stork] attribute value
const baseUrl = document.currentScript.getAttribute('data-emanote-base-url') || '/';
const indexUrl = baseUrl + '-/stork.st';
if (document.readyState !== 'complete') {
window.addEventListener('load', function () {
2022-11-23 11:22:29 +00:00
stork.initialize(baseUrl + '_emanote-static/stork/stork.wasm');
2022-08-24 14:52:32 +00:00
stork.register(indexName, indexUrl);
});
document.addEventListener('keydown', event => {
if (window.emanote.stork.searchShown & & event.key === 'Escape') {
window.emanote.stork.clearSearch();
event.preventDefault();
} else if ((event.key == 'k' || event.key == 'K') & & (event.ctrlKey || event.metaKey)) {
window.emanote.stork.toggleSearch();
event.preventDefault();
}
});
} else {
// Override existing on Ema's hot-reload
stork.register(indexName, indexUrl, { forceOverwrite: true });
}
}
};
window.emanote.stork.init();
< / script >
< / head >
<!-- DoNotFormat -->
<!-- DoNotFormat -->
< body class = 'bg-gray-400 overflow-y-scroll' >
< div class = 'container mx-auto' >
< nav id = 'breadcrumbs' class = 'w-full text-gray-700 md:hidden' >
< div class = 'flex justify-left' >
< div class = 'w-full px-2 py-2 bg-gray-50' >
< ul class = 'flex flex-wrap text-lg' >
< li class = 'inline-flex items-center' >
< img style = 'width: 1rem;' src = 'favicon.svg' / >
< / li >
< li class = 'inline-flex items-center' >
< a class = 'px-1 font-bold' href = '' >
Home
< / a >
< svg fill = 'currentColor' viewBox = '0 0 20 20' class = 'w-auto h-5 text-gray-400' >
< path fill-rule = 'evenodd' d = 'M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule = 'evenodd' > < / path >
< / svg >
< / li >
< li class = 'inline-flex items-center' >
2022-11-23 11:22:29 +00:00
< a class = 'px-1 font-bold' href = 'Coding' >
Coding
< / a >
< svg fill = 'currentColor' viewBox = '0 0 20 20' class = 'w-auto h-5 text-gray-400' >
< path fill-rule = 'evenodd' d = 'M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule = 'evenodd' > < / path >
< / svg >
< / li >
< li class = 'inline-flex items-center' >
< a class = 'px-1 font-bold' href = 'Coding/Haskell' >
2022-08-24 14:52:32 +00:00
Haskell
< / a >
< svg fill = 'currentColor' viewBox = '0 0 20 20' class = 'w-auto h-5 text-gray-400' >
< path fill-rule = 'evenodd' d = 'M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule = 'evenodd' > < / path >
< / svg >
< / li >
< / ul >
< / div >
< button class = 'inline px-2 py-1 bg-gray-50 outline-none cursor-pointer focus:outline-none' title = 'Search (Ctrl+K)' type = 'button' onclick = 'window.emanote.stork.toggleSearch()' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' style = 'width: 1rem;' class = 'hover:text-purple-700' f
2022-08-24 14:52:32 +00:00
fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'>
< path stroke-linecap = 'round' stroke-linejoin = 'round' d = 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' > < / path >
< / svg >
< / button >
2022-08-25 04:18:18 +00:00
< button class = 'inline px-2 py-1 text-white bg-purple-600 outline-none cursor-pointer focus:outline-none' title = 'Toggle sidebar' type = 'button' onclick = "toggleHidden('sidebar')" >
2022-08-24 14:52:32 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4' fill = 'none' viewBox = '0 0 24 24' stroke = 'currentColor' >
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M4 6h16M4 12h16M4 18h16' > < / path >
< / svg >
< / button >
< script >
function toggleHidden(elemId) {
document.getElementById(elemId).classList.toggle("hidden");
}
< / script >
< / div >
< / nav >
< div id = 'container' class = 'flex flex-nowrap flex-col md:flex-row bg-gray-50 md:mt-8 md:shadow-2xl md:mb-8' >
<!-- Sidebar column -->
< nav id = 'sidebar' class = 'flex-shrink hidden leading-relaxed md:block md:sticky md:top-0 md:h-full md:w-48 xl:w-64' >
< div class = 'px-2 py-2 text-gray-800' >
< div id = 'indexing-links' class = 'flex flex-row float-right p-2 space-x-2 text-gray-500' >
< a href = '-/tags' title = 'View tags' >
2022-08-25 04:18:18 +00:00
< svg style = 'width: 1rem;' class = 'hover:text-purple-700' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
2022-08-24 14:52:32 +00:00
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z' >
< / path >
< / svg >
< / a >
< a href = '-/all' title = 'Expand full tree' >
2022-08-25 04:18:18 +00:00
< svg style = 'width: 1rem;' class = 'hover:text-purple-700' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
2022-08-24 14:52:32 +00:00
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4' >
< / path >
< / svg >
< / a >
< a title = 'Search (Ctrl+K)' class = 'cursor-pointer' onclick = 'window.emanote.stork.toggleSearch()' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' style = 'width: 1rem;' class = 'hover:text-purple-700' f
2022-08-24 14:52:32 +00:00
fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'>
< path stroke-linecap = 'round' stroke-linejoin = 'round' d = 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' > < / path >
< / svg >
< / a >
< / div >
< div id = 'site-logo' class = 'pl-2' >
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< a href = '' title = 'Go to Home' >
<!-- The style width attribute here is to prevent huge
icon from displaying at those rare occasions when Tailwind
hasn't kicked in immediately on page load
-->
< img style = 'width: 1rem;' class = 'transition transform hover:scale-110 hover:opacity-80' src = 'favicon.svg' / >
< / a >
< a class = 'font-bold truncate' title = 'Go to Home' href = '' >
Home
< / a >
< / div >
< / div >
<!-- Variable bindings for this tree -->
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-500' viewBox = '0 0 20 20' fill = 'currentColor' >
< path d = 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' > < / path >
2022-08-24 14:52:32 +00:00
< / svg >
2022-08-25 04:18:18 +00:00
< a class = 'hover:underline truncate' title = 'About' href = 'About' >
About
2022-08-24 14:52:32 +00:00
< / a >
2022-08-25 04:18:18 +00:00
< span class = 'text-gray-300' title = '3 children inside' >
3
< / span >
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-500' viewBox = '0 0 20 20' fill = 'currentColor' >
< path d = 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' > < / path >
2022-08-24 14:52:32 +00:00
< / svg >
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
2022-08-25 04:18:18 +00:00
< a class = 'hover:underline truncate' title = 'Android' href = 'Android' >
2022-08-24 14:52:32 +00:00
Android
< / a >
2022-08-25 04:18:18 +00:00
< span class = 'text-gray-300' title = '1 children inside' >
1
< / span >
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-700' viewBox = '0 0 20 20' fill = 'currentColor' >
< path fill-rule = 'evenodd' d = 'M2 6a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1H8a3 3 0 00-3 3v1.5a1.5 1.5 0 01-3 0V6z' clip-rule = 'evenodd' > < / path >
< path d = 'M6 12a2 2 0 012-2h8a2 2 0 012 2v2a2 2 0 01-2 2H2h2a2 2 0 002-2v-2z' > < / path >
< / svg >
2022-11-23 11:22:29 +00:00
< a class = 'font-bold hover:underline truncate' title = 'Coding' href = 'Coding' >
Coding
< / a >
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
<!-- Variable bindings for this tree -->
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-700' viewBox = '0 0 20 20' fill = 'currentColor' >
< path fill-rule = 'evenodd' d = 'M2 6a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1H8a3 3 0 00-3 3v1.5a1.5 1.5 0 01-3 0V6z' clip-rule = 'evenodd' > < / path >
< path d = 'M6 12a2 2 0 012-2h8a2 2 0 012 2v2a2 2 0 01-2 2H2h2a2 2 0 002-2v-2z' > < / path >
< / svg >
< a class = 'font-bold hover:underline truncate' title = 'Haskell' href = 'Coding/Haskell' >
2022-08-24 14:52:32 +00:00
Haskell
< / a >
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
<!-- Variable bindings for this tree -->
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
2022-11-23 11:22:29 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg class = 'w-4 h-4 flex-shrink-0 inline' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' >
< / path >
< / svg >
< a class = 'hover:underline truncate' title = 'Talks und Posts zu Haskell' href = 'Coding/Haskell/Advantages' >
Talks und Posts zu Haskell
< / a >
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
2022-08-24 14:52:32 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-500' viewBox = '0 0 20 20' fill = 'currentColor' >
< path d = 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' > < / path >
2022-08-24 14:52:32 +00:00
< / svg >
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
2022-11-23 11:22:29 +00:00
< a class = 'hover:underline truncate' title = 'Code-Snippets' href = 'Coding/Haskell/Code%20Snippets' >
2022-08-24 14:52:32 +00:00
Code-Snippets
< / a >
2022-08-25 04:18:18 +00:00
< span class = 'text-gray-300' title = '2 children inside' >
2
< / span >
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg class = 'w-4 h-4 flex-shrink-0 inline' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' >
< / path >
< / svg >
2022-11-23 11:22:29 +00:00
< a class = 'hover:underline truncate' title = 'Fortgeschrittene funktionale Programmierung in Haskell' href = 'Coding/Haskell/FFPiH' >
2022-08-24 14:52:32 +00:00
Fortgeschrittene funktionale Programmierung in Haskell
< / a >
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg class = 'w-4 h-4 flex-shrink-0 inline' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' >
< / path >
< / svg >
2022-11-23 11:22:29 +00:00
< a class = 'font-bold text-purple-600 hover:underline truncate' title = 'Lenses' href = 'Coding/Haskell/Lenses' >
2022-08-24 14:52:32 +00:00
Lenses
< / a >
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
2022-11-23 11:22:29 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-500' viewBox = '0 0 20 20' fill = 'currentColor' >
< path d = 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' > < / path >
< / svg >
< a class = 'hover:underline truncate' title = 'Webapp-Development in Haskell' href = 'Coding/Haskell/Webapp-Example' >
Webapp-Development in Haskell
< / a >
< span class = 'text-gray-300' title = '2 children inside' >
2
< / span >
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
< / div >
<!-- Variable bindings for this tree -->
2022-08-24 14:52:32 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg class = 'w-4 h-4 flex-shrink-0 inline' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' >
< / path >
< / svg >
2022-11-23 11:22:29 +00:00
< a class = 'hover:underline truncate' title = 'Openapi-generator' href = 'Coding/OpenAPI' >
Openapi-generator
2022-08-24 14:52:32 +00:00
< / a >
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
2022-11-23 11:22:29 +00:00
< / div >
<!-- Variable bindings for this tree -->
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg class = 'w-4 h-4 flex-shrink-0 inline' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' >
< / path >
< / svg >
< a class = 'hover:underline truncate' title = 'Logik für Dummies' href = 'Logik' >
Logik für Dummies
< / a >
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
2022-08-24 14:52:32 +00:00
2022-08-25 03:26:25 +00:00
2022-08-24 14:52:32 +00:00
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-500' viewBox = '0 0 20 20' fill = 'currentColor' >
< path d = 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' > < / path >
2022-08-24 14:52:32 +00:00
< / svg >
2022-11-23 11:22:29 +00:00
< a class = 'hover:underline truncate' title = 'Opinions' href = 'Opinions' >
Opinions
2022-08-25 03:26:25 +00:00
< / a >
2022-08-25 04:18:18 +00:00
< span class = 'text-gray-300' title = '2 children inside' >
2
< / span >
2022-08-25 03:26:25 +00:00
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
< svg class = 'w-4 h-4 flex-shrink-0 inline' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' >
< / path >
< / svg >
2022-11-23 11:22:29 +00:00
< a class = 'hover:underline truncate' title = 'Todo' href = 'TODO' >
Todo
2022-08-24 14:52:32 +00:00
< / a >
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-500' viewBox = '0 0 20 20' fill = 'currentColor' >
< path d = 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' > < / path >
2022-08-24 14:52:32 +00:00
< / svg >
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
2022-08-25 04:18:18 +00:00
< a class = 'hover:underline truncate' title = 'Uni' href = 'Uni' >
2022-08-24 14:52:32 +00:00
Uni
< / a >
2022-08-25 04:18:18 +00:00
< span class = 'text-gray-300' title = '2 children inside' >
2
< / span >
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
<!-- Variable bindings for this tree -->
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
<!-- Rendering of this tree -->
< div class = 'pl-2' >
<!-- Node's rootLabel -->
< div class = 'flex items-center my-2 space-x-2 justify-left' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-4 h-4 flex-shrink-0 inline text-gray-500' viewBox = '0 0 20 20' fill = 'currentColor' >
< path d = 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' > < / path >
2022-08-24 14:52:32 +00:00
< / svg >
2022-08-25 04:18:18 +00:00
2022-08-24 14:52:32 +00:00
2022-08-25 04:18:18 +00:00
< a class = 'hover:underline truncate' title = 'Unix' href = 'Unix' >
2022-08-24 14:52:32 +00:00
Unix
< / a >
2022-08-25 04:18:18 +00:00
< span class = 'text-gray-300' title = '1 children inside' >
1
< / span >
2022-08-24 14:52:32 +00:00
< / div >
<!-- Node's children forest, displayed only on active trees
TODO: Use < details > to toggle visibility?
-->
< / div >
< / div >
< / nav >
<!-- Main body column -->
< div class = 'flex-1 w-full overflow-x-auto bg-white' >
< main class = 'px-4 py-4' >
<!-- DoNotFormat -->
<!-- DoNotFormat -->
< nav id = 'uptree' class = 'flipped tree' style = 'transform-origin: 50%;' >
< ul class = 'root' >
< li >
< ul >
< li >
< div class = 'text-gray-900 forest-link' >
2022-11-23 11:22:29 +00:00
< a href = 'Coding/Haskell/Advantages' >
2022-08-24 14:52:32 +00:00
Talks und Posts zu Haskell
< / a >
< / div >
< ul >
< li >
< div class = 'text-gray-900 forest-link' >
2022-11-23 11:22:29 +00:00
< a href = 'Coding/Haskell' >
2022-08-24 14:52:32 +00:00
Haskell
< / a >
< / div >
2022-11-23 11:22:29 +00:00
< ul >
< li >
< div class = 'text-gray-900 forest-link' >
< a href = 'Coding' >
Coding
< / a >
< / div >
2022-08-24 14:52:32 +00:00
< ul >
< li >
< div class = 'text-gray-900 forest-link' >
< a href = '' >
Home
< / a >
< / div >
< / li >
< / ul >
< / li >
< / ul >
2022-11-23 11:22:29 +00:00
< / li >
< / ul >
2022-08-24 14:52:32 +00:00
< / li >
< li >
< div class = 'text-gray-900 forest-link' >
2022-11-23 11:22:29 +00:00
< a href = 'Coding/Haskell/FFPiH' >
2022-08-24 14:52:32 +00:00
Fortgeschrittene funktionale Programmierung in Haskell
< / a >
< / div >
< ul >
< li >
< div class = 'text-gray-900 forest-link' >
< a href = 'About/CV' >
2022-08-25 04:18:18 +00:00
About me
2022-08-24 14:52:32 +00:00
< / a >
< / div >
< ul >
< li >
< div class = 'text-gray-900 forest-link' >
< a href = 'About' >
About
< / a >
< / div >
< / li >
< / ul >
< / li >
< li >
< div class = 'text-gray-900 forest-link' >
2022-08-25 04:18:18 +00:00
< a href = 'Uni/Extracurricular' >
2022-08-24 14:52:32 +00:00
Studium generale / University-Life
< / a >
< / div >
2022-08-25 04:18:18 +00:00
< ul >
< li >
< div class = 'text-gray-900 forest-link' >
< a href = 'Uni' >
Uni
< / a >
< / div >
< / li >
< / ul >
2022-08-24 14:52:32 +00:00
< / li >
< / ul >
< / li >
< / ul >
< / li >
< / ul >
< / nav >
2022-08-25 04:18:18 +00:00
< h1 class = 'flex items-end justify-center mb-4 p-3 bg-purple-100 text-5xl font-extrabold text-black rounded' >
2022-08-24 14:52:32 +00:00
< a class = 'z-40 tracking-tighter ' >
Lenses
< / a >
< / h1 >
< article class = 'overflow-auto' >
<!-- What goes in this file will appear on top of note body -->
< h2 id = 'wofür-brauchen-wir-das-überhaupt' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Wofür brauchen wir das überhaupt?< / h2 >
< p class = 'mb-3' >
Die Idee dahinter ist, dass man Zugriffsabstraktionen über Daten verknüpfen kann. Als einfachen Datenstruktur kann man einen Record mit der entsprechenden Syntax nehmen.
< / p >
2022-08-25 03:26:25 +00:00
< h3 id = 'beispiel' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Beispiel< / h3 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data Person = P { name :: String
2022-08-24 14:52:32 +00:00
, addr :: Address
, salary :: Int }
data Address = A { road :: String
, city :: String
, postcode :: String }
-- autogeneriert unten anderem: addr :: Person -> Address
setName :: String -> Person -> Person
setName n p = p { name = n } --record update notation
setPostcode :: String -> Person -> Person
setPostcode pc p
= p { addr = addr p { postcode = pc } }
-- update of a record inside a record< / code > < / pre > < / div > < h3 id = 'probleme' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Probleme< / h3 >
< p class = 'mb-3' >
Probleme mit diesem Code:
< / p >
< ul class = 'my-3 ml-6 space-y-1 list-disc' >
< li >
für 1-Dimensionale Felder ist die record-syntax ok.
< / li >
< li >
tiefere Ebenen nur umständlich zu erreichen
< / li >
< li >
eigentlich wollen wir nur pe in p setzen, müssen aber über addr etc. gehen.
< / li >
< li >
wir brauchen wissen über die “Zwischenstrukturen”, an denen wir nicht interessiert sind
< / li >
< / ul >
2022-08-25 03:26:25 +00:00
< h3 id = 'was-wir-gern-hätten' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Was wir gern hätten< / h3 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data Person = P { name :: String
2022-08-24 14:52:32 +00:00
, addr :: Address
, salary :: Int }
-- a lens for each field
lname :: Lens' Person String
laddr :: Lens' Person Adress
lsalary :: Lens' Person Int
-- getter/setter for them
view :: Lens' s a -> s -> a
set :: Lens' s a -> a -> s -> s
-- lens-composition
composeL :: Lens' s1 s2 -> Lens s2 a -> Lens' s1 a< / code > < / pre > < / div > < h3 id = 'wie-uns-das-hilft' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Wie uns das hilft< / h3 >
< p class = 'mb-3' >
Mit diesen Dingen (wenn wir sie hätten) könnte man dann
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data Person = P { name :: String
2022-08-24 14:52:32 +00:00
, addr :: Address
, salary :: Int }
data Address = A { road :: String
, city :: String
, postcode :: String }
setPostcode :: String -> Person -> Person
setPostcode pc p
= set (laddr `composeL` lpostcode) pc p< / code > < / pre > < / div >
< p class = 'mb-3' >
machen und wäre fertig.
< / p >
2022-08-25 03:26:25 +00:00
< h2 id = 'trivialer-ansatz' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Trivialer Ansatz< / h2 > < h3 id = 'gettersetter-als-lens-methoden' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Getter/Setter als Lens-Methoden< / h3 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data LensR s a = L { viewR :: s -> a
2022-08-24 14:52:32 +00:00
, setR :: a -> s -> s }
composeL (L v1 u1) (L v2 u2)
= L (\s -> v2 (v1 s))
(\a s -> u1 (u2 a (v1 s)) s)< / code > < / pre > < / div > < h3 id = 'wieso-ist-das-schlecht' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Wieso ist das schlecht?< / h3 >
< ul class = 'my-3 ml-6 space-y-1 list-disc' >
< li >
extrem ineffizient
< / li >
< / ul >
< p class = 'mb-3' >
Auslesen traversiert die Datenstruktur, dann wird die Funktion angewendet und zum setzen wird die Datenstruktur erneut traversiert:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > over :: LensR s a -> (a -> a) -> s -> s
2022-08-24 14:52:32 +00:00
over ln f s = setR l (f (viewR l s)) s< / code > < / pre > < / div >
< ul class = 'my-3 ml-6 space-y-1 list-disc' >
< li >
Lösung: modify-funktion hinzufügen
< / li >
< / ul >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data LensR s a
2022-08-24 14:52:32 +00:00
= L { viewR :: s -> a
, setR :: a -> s -> s
, mod :: (a-> a) -> s -> s
, modM :: (a-> Maybe a) -> s -> Maybe s
, modIO :: (a-> IO a) -> s -> IO s }< / code > < / pre > < / div >
< p class = 'mb-3' >
Neues Problem: Für jeden Spezialfall muss die Lens erweitert werden.
< / p >
< h3 id = 'something-in-common' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Something in common< / h3 >
< p class = 'mb-3' >
Man kann alle Monaden abstrahieren. Functor reicht schon:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data LensR s a
2022-08-24 14:52:32 +00:00
= L { viewR :: s -> a
, setR :: a -> s -> s
, mod :: (a-> a) -> s -> s
, modF :: Functor f => (a-> f a) -> s -> f s }< / code > < / pre > < / div >
< p class = 'mb-3' >
Idee: Die 3 darüberliegenden durch modF ausdrücken.
< / p >
< h3 id = 'typ-einer-lens' class = 'mt-6 mb-2 text-3xl font-bold text-gray-700' > Typ einer Lens< / h3 >
< p class = 'mb-3' >
Wenn man das berücksichtigt, dann hat einen Lens folgenden Typ:
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > type Lens' s a = forall f. Functor f
=> (a -> f a) -> s -> f s< / code > < / pre > < / div >
< p class = 'mb-3' >
Allerdings haben wir dann noch unseren getter/setter:
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data LensR s a = L { viewR :: s -> a
, setR :: a -> s -> s }< / code > < / pre > < / div >
< p class = 'mb-3' >
Stellt sich raus: Die sind isomorph! Auch wenn die von den Typen her komplett anders aussehen.
< / p >
2022-08-25 03:26:25 +00:00
< h2 id = 'benutzen-einer-lens-als-setter' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Benutzen einer Lens als Setter< / h2 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > set :: Lens' s a -> (a -> s -> s)
2022-08-24 14:52:32 +00:00
set ln a s = --...umm...
--:t ln => (a -> f a) -> s -> f s
-- => get s out of f s to return it< / code > < / pre > < / div >
< p class = 'mb-3' >
Wir können für f einfach die “Identity”-Monade nehmen, die wir nachher wegcasten können.
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > newtype Identity a = Identity a
2022-08-24 14:52:32 +00:00
-- Id :: a -> Identity a
runIdentity :: Identity s -> s
runIdentity (Identity x) = x
instance Functor Identity where
fmap f (Identity x) = Identity (f x)< / code > < / pre > < / div >
< p class = 'mb-3' >
somit ist set einfach nur
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > set :: Lens' s a -> (a -> s -> s)
2022-08-24 14:52:32 +00:00
set ln x s
= runIdentity (ls set_fld s)
where
set_fld :: a -> Identity a
set_fld _ = Identity x
-- a was the OLD value.
-- We throw that away and set the new value< / code > < / pre > < / div >
< p class = 'mb-3' >
oder kürzer (für nerds wie den Autor der Lens-Lib)
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > set :: Lens' s a -> (a -> s -> s)
set ln x = runIdentity . ln (Identity . const x)< / code > < / pre > < / div > < h2 id = 'benutzen-einer-lens-als-modify' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Benutzen einer Lens als Modify< / h2 >
< p class = 'mb-3' >
Dasselbe wie Set, nur dass wir den Parameter nicht entsorgen, sondern in die mitgelieferte Funktion stopfen.
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > over :: Lens' s a -> (a -> a) -> s -> s
2022-08-25 03:26:25 +00:00
over ln f = runIdentity . ln (Identity . f)< / code > < / pre > < / div > < h2 id = 'benutzen-einer-lens-als-getter' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Benutzen einer Lens als Getter< / h2 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > view :: Lens' s a -> (s -> a)
2022-08-24 14:52:32 +00:00
view ln s = --...umm...
--:t ln => (a -> f a) -> s -> f s
-- => get a out of the (f s) return-value
-- Wait, WHAT?< / code > < / pre > < / div >
< p class = 'mb-3' >
Auch hier gibt es einen netten Funktor. Wir packen das “a” einfach in das “f” und werfen das “s” am Ende weg.
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > newtype Const v a = Const v
2022-08-24 14:52:32 +00:00
getConst :: Const v a -> v
getConst (Const x) = x
instance Functor (Const v) where
fmap f (Const x) = Const x
-- throw f away. Nothing changes our const!< / code > < / pre > < / div >
< p class = 'mb-3' >
somit ergibt sich
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > view :: Lens' s a -> (s -> a)
2022-08-24 14:52:32 +00:00
view ln s
= getConst (ln Const s)
-- Const :: s -> Const a s< / code > < / pre > < / div >
< p class = 'mb-3' >
oder nerdig
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > view :: Lens' s a -> (s -> a)
view ln = getConst . ln Const< / code > < / pre > < / div > < h2 id = 'lenses-bauen' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Lenses bauen< / h2 >
< p class = 'mb-3' >
Nochmal kurz der Typ:
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > type Lens' s a = forall f. Functor f
=> (a -> f a) -> s -> f s< / code > < / pre > < / div >
< p class = 'mb-3' >
Für unser Personen-Beispiel vom Anfang:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data Person = P { _name :: String, _salary :: Int }
2022-08-24 14:52:32 +00:00
name :: Lens' Person String
-- name :: Functor f => (String -> f String)
-- -> Person -> f Person
name elt_fn (P n s)
= fmap (\n' -> P n' s) (elt_fn n)
-- fmap :: Functor f => (a-> b) -> f a -> f b - der Funktor, der alles verknüpft
-- \n' -> .. :: String -> Person - Funktion um das Element zu lokalisieren (WO wird ersetzt/gelesen/...)
-- elt_fn n :: f String - Funktion um das Element zu verändern (setzen, ändern, ...)< / code > < / pre > < / div >
< p class = 'mb-3' >
Die Lambda-Funktion ersetzt einfach den Namen. Häufig sieht man auch
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > name elt_fn (P n s)
= (\n' -> P n' s) < $> (elt_fn n)
2022-08-25 03:26:25 +00:00
-- | Focus | |Function|< / code > < / pre > < / div > < h2 id = 'wie-funktioniert-das-intern' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Wie funktioniert das intern?< / h2 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > view name (P {_name="Fred", _salary=100})
2022-08-24 14:52:32 +00:00
-- inline view-function
= getConst (name Const (P {_name="Fred", _salary=100})
-- inline name
= getConst (fmap (\n' -> P n' 100) (Const "Fred"))
-- fmap f (Const x) = Const x - Definition von Const
= getConst (Const "Fred")
-- getConst (Const x) = x
= "Fred"< / code > < / pre > < / div >
< p class = 'mb-3' >
Dieser Aufruf hat KEINE Runtime-Kosten, weil der Compiler direkt die Adresse des Feldes einsetzen kann. Der gesamte Boilerplate-Code wird vom Compiler wegoptimiert.
< / p >
< p class = 'mb-3' >
Dies gilt für jeden Funktor mit newtype, da das nur ein Typalias ist.
< / p >
< h2 id = 'composing-lenses-und-deren-benutzung' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Composing Lenses und deren Benutzung< / h2 >
< p class = 'mb-3' >
Wie sehen denn die Typen aus?
< / p >
< p class = 'mb-3' >
Wir wollen ein
< / p >
< blockquote class = 'py-0.5 px-4 mb-3 italic border-l-4 bg-gray-50 text-gray-600 border-gray-400 quote' >
< p class = 'mb-3' >
Lens’ s1 s2 -> Lens’ s2 a -> Lens’ s1 a
< / p >
< / blockquote >
< p class = 'mb-3' >
Wir haben 2 Lenses
< / p >
< blockquote class = 'py-0.5 px-4 mb-3 italic border-l-4 bg-gray-50 text-gray-600 border-gray-400 quote' >
< p class = 'mb-3' >
ln1 :: (s2 -> f s2) -> (s1 -> f s1) ln2 :: (a -> f a) -> (s2 -> f s2)
< / p >
< / blockquote >
< p class = 'mb-3' >
wenn man scharf hinsieht, kann man die verbinden
< / p >
< blockquote class = 'py-0.5 px-4 mb-3 italic border-l-4 bg-gray-50 text-gray-600 border-gray-400 quote' >
< p class = 'mb-3' >
ln1 . ln2 :: (a -> f s) -> (s1 -> f s1)
< / p >
< / blockquote >
< p class = 'mb-3' >
und erhält eine Lens. Sogar die Gewünschte!< br / > Somit ist Lens-Composition einfach nur Function-Composition (.).
< / p >
< h2 id = 'automatisieren-mit-template-haskell' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Automatisieren mit Template-Haskell< / h2 >
< p class = 'mb-3' >
Der Code um die Lenses zu bauen ist für records immer Identisch:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data Person = P { _name :: String, _salary :: Int }
2022-08-24 14:52:32 +00:00
name :: Lens' Person String
name elt_fn (P n s) = (\n' -> P n' s) < $> (elt_fn n)< / code > < / pre > < / div >
< p class = 'mb-3' >
Daher kann man einfach
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > import Control.Lens.TH
2022-08-24 14:52:32 +00:00
data Person = P { _name :: String, _salary :: Int }
$(makeLenses ''Person)< / code > < / pre > < / div >
< p class = 'mb-3' >
nehmen, was einem eine Lens für “name” und eine Lens für “salary” generiert.< br / > Mit anderen Templates kann man auch weitere Dinge steuern (etwa wofür Lenses generiert werden, welches Prefix (statt _) man haben will etc. pp.).
< / p >
< p class = 'mb-3' >
Will man das aber haben, muss man selbst in den Control.Lens.TH-Code schauen.
< / p >
2022-08-25 03:26:25 +00:00
< h2 id = 'lenses-für-den-beispielcode' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Lenses für den Beispielcode< / h2 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > import Control.Lens.TH
2022-08-24 14:52:32 +00:00
data Person = P { _name :: String
, _addr :: Address
, _salary :: Int }
data Address = A { _road :: String
, _city :: String
, _postcode :: String }
$(makeLenses ''Person)
$(makeLenses ''Address)
setPostcode :: String -> Person -> Person
2022-08-25 03:26:25 +00:00
setPostcode pc p = set (addr . postcode) pc p< / code > < / pre > < / div > < h2 id = 'shortcuts-mit-line-noise' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Shortcuts mit “Line-Noise”< / h2 > < div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > -- ...
2022-08-24 14:52:32 +00:00
setPostcode :: String -> Person -> Person
setPostcode pc p = addr . postcode .~ pc $ p
-- | Focus |set|to what|in where
getPostcode :: Person -> String
getPostcode p = p ^. $ addr . postcode
-- |from|get| Focus |< / code > < / pre > < / div >
< p class = 'mb-3' >
Es gibt drölf-zillionen weitere Infix-Operatoren (für Folds, Listenkonvertierungen, -traversierungen, …)
< / p >
< h2 id = 'virtuelle-felder' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Virtuelle Felder< / h2 >
< p class = 'mb-3' >
Man kann mit Lenses sogar Felder emulieren, die gar nicht da sind. Angenommen folgender Code:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data Temp = T { _fahrenheit :: Float }
2022-08-24 14:52:32 +00:00
$(makeLenses ''Temp)
-- liefert Lens: fahrenheit :: Lens Temp Float
centigrade :: Lens Temp Float
centigrade centi_fn (T faren)
= (\centi' -> T (cToF centi'))
< $> (centi_fn (fToC faren))
-- cToF & fToC as Converter-Functions defined someplace else< / code > < / pre > < / div >
< p class = 'mb-3' >
Hiermit kann man dann auch Funktionen, die auf Grad-Celsius rechnen auf Daten anwenden, die eigenlich nur Fahrenheit speichern, aber eine Umrechnung bereitstellen. Analog kann man auch einen Zeit-Datentypen definieren, der intern mit Sekunden rechnet (und somit garantiert frei von Fehlern wie -3 Minuten oder 37 Stunden ist)
< / p >
< h2 id = 'non-record-strukturen' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Non-Record Strukturen< / h2 >
< p class = 'mb-3' >
Das ganze kann man auch parametrisieren und auf Non-Record-Strukturen anwenden. Beispielhaft an einer Map verdeutlicht:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > -- from Data.Lens.At
2022-08-24 14:52:32 +00:00
at :: Ord k => k -> Lens' (Map k v) (Maybe v)
-- oder identisch, wenn man die Lens' auflöst:
at :: Ord k, forall f. Functor f => k -> (Maybe v -> f Maybe v) -> Map k v -> f Map k v
at k mb_fn m
= wrap < $> (mb_fn mv)
where
mv = Map.lookup k m
wrap :: Maybe v -> Map k v
wrap (Just v') = Map.insert k v' m
wrap Nothing = case mv of
Nothing -> m
Just _ -> Map.delete k m
-- mb_fn :: Maybe v -> f Maybe v< / code > < / pre > < / div > < h2 id = 'weitere-beispiele' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Weitere Beispiele< / h2 >
< ul class = 'my-3 ml-6 space-y-1 list-disc' >
< li >
< p class = 'mb-3' >
Bitfields auf Strukturen die Bits haben (Ints, …) in Data.Bits.Lens
< / p >
< / li >
< li >
< p class = 'mb-3' >
Web-scraper in Package hexpat-lens
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > p ^.. _HTML' . to allNodes
2022-08-24 14:52:32 +00:00
. traverse . named "a"
. traverse . ix "href"
. filtered isLocal
. to trimSpaces< / code > < / pre > < / div >
< p class = 'mb-3' >
Zieht alle externen Links aus dem gegebenen HTML-Code in p um weitere ziele fürs crawlen zu finden.
< / p >
< / li >
< / ul >
< h2 id = 'erweiterungen' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Erweiterungen< / h2 >
< p class = 'mb-3' >
Bisher hatten wir Lenses nur auf Funktoren F. Die nächstmächtigere Klasse ist Applicative.
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > type Traversal' s a = forall f. Applicative f
2022-08-24 14:52:32 +00:00
=> (a -> f a) -> (s -> f s)< / code > < / pre > < / div >
< p class = 'mb-3' >
Da wir den Container identisch lassen (weder s noch a wurde angefasst) muss sich etwas anderes ändern. Statt eines einzelnen Focus erhalten wir viele Foci.
< / p >
< p class = 'mb-3' >
Was ist ein Applicative überhaupt? Eine schwächere Monade (nur 1x Anwendung und kein Bind - dafür kann man die beliebig oft hintereinanderhängen).
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > class Functor f => Applicative f where
2022-08-24 14:52:32 +00:00
pure :: a -> f a
(< *> ) :: f (a -> b) -> f a -> f b
-- Monade als Applicative:
pure = return
mf < *> mx = do { f < - mf; x < - mx; return (f x) }< / code > < / pre > < / div >
< p class = 'mb-3' >
Recap: Was macht eine Lens:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > data Adress = A { _road :: String
2022-08-24 14:52:32 +00:00
, _city :: String
, _postcode :: String }
road :: Lens' Adress String
road elt_fn (A r c p) = (\r' -> A r' c p) < $> (elt_fn r)
-- | "Hole" | | Thing to put in|< / code > < / pre > < / div >
< p class = 'mb-3' >
Wenn man nun road & city gleichzeitig bearbeiten will:
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > addr_strs :: Traversal' Address String
2022-08-24 14:52:32 +00:00
addr_strs elt_fn (A r c p)
= ... (\r' c' -> A r' c' p) .. (elt_fn r) .. (elt_fn c) ..
-- | function with 2 "Holes"| first Thing | second Thing< / code > < / pre > < / div >
< p class = 'mb-3' >
fmap kann nur 1 Loch stopfen, aber nicht mit n Löchern umgehen. Applicative mit < *> kann das.< br / > Somit gibt sich
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > addr_strs :: Traversal' Address String
2022-08-24 14:52:32 +00:00
addr_strs elt_fn (A r c p)
= pure (\r' c' -> A r' c' p) < *> (elt_fn r) < *> (elt_fn c)
-- lift in Appl. | function with 2 "Holes"| first Thing | second Thing
-- oder kürzer
addr_strs :: Traversal' Address String
addr_strs elt_fn (A r c p)
= (\r' c' -> A r' c' p) < $> (elt_fn r) < *> (elt_fn c)
-- pure x < *> y == x < $> y< / code > < / pre > < / div >
< p class = 'mb-3' >
Wie würd eine modify-funktion aussehen?
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > over :: Lens' s a -> (a -> a) -> s -> s
over ln f = runIdentity . ln (Identity . f)
over :: Traversal' s a -> (a -> a) -> s -> s
over ln f = runIdentity . ln (Identity . f)< / code > < / pre > < / div >
< p class = 'mb-3' >
Der Code ist derselbe - nur der Typ ist generischer. Auch die anderen Dinge funktioniert diese Erweiterung (für Identity und Const muss man noch ein paar dummy-Instanzen schreiben um sie von Functor auf Applicative oder Monad zu heben
< / p >
< ul class = 'my-3 ml-6 space-y-1 list-disc' >
< li >
konkret reicht hier die Instanzierung von Monoid). In der Lens-Library ist daher meist Monad m statt Functor f gefordert.
< / li >
< / ul >
< h2 id = 'wozu-dienen-die-erweiterungen' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Wozu dienen die Erweiterungen?< / h2 >
< p class = 'mb-3' >
Man kann mit Foci sehr selektiv vorgehen. Auch kann man diese durch Funktionen steuern. Beispisweise eine Funktion anwenden auf
< / p >
< ul class = 'my-3 ml-6 space-y-1 list-disc' >
< li >
Jedes 2. Listenelement
< / li >
< li >
Alle graden Elemente in einem Baum
< / li >
< li >
Alle Namen in einer Tabelle, deren Gehalt > 10.000€ ist
< / li >
< / ul >
< p class = 'mb-3' >
Traversals und Lenses kann man trivial kombinieren (< code class = 'py-0.5 px-0.5 bg-gray-100' > lens . lens< / code > => < code class = 'py-0.5 px-0.5 bg-gray-100' > lens< / code > , < code class = 'py-0.5 px-0.5 bg-gray-100' > lens . traversal< / code > => < code class = 'py-0.5 px-0.5 bg-gray-100' > traversal< / code > etc.)
< / p >
< h2 id = 'wie-es-in-lens-wirklich-aussieht' class = 'inline-block mt-6 mb-4 text-4xl font-bold text-gray-700 border-b-2' > Wie es in Lens wirklich aussieht< / h2 >
< p class = 'mb-3' >
In diesem Artikel wurde nur auf Monomorphic Lenses eingegangen. In der richtigen Library ist eine Lens
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > type Lens' s a = Lens s s a a
type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)< / code > < / pre > < / div >
< p class = 'mb-3' >
sodass sich auch die Typen ändern können um z.B. automatisch einen Konvertierten (sicheren) Typen aus einer unsicheren Datenstruktur zu geben.
< / p >
< p class = 'mb-3' >
Die modify-Funktion over ist auch
< / p >
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > > over :: Profunctor p => Setting p s t a b -> p a b -> s -> t< / code > < / pre > < / div >
< blockquote class = 'py-0.5 px-4 mb-3 italic border-l-4 bg-gray-50 text-gray-600 border-gray-400 quote' >
< p class = 'mb-3' >
< em > Edward is deeply in thrall to abstractionitis< / em > - Simon Peyton Jones
< / p >
< / blockquote >
< p class = 'mb-3' >
Lens alleine definiert 39 newtypes, 34 data-types und 194 Typsynonyme…< br / > Ausschnitt
< / p >
2022-08-25 03:26:25 +00:00
< div class = 'py-0.5 mb-3 text-sm' > < pre > < code class = 'haskell language-haskell' > -- traverseOf :: Functor f => Iso s t a b -> (a -> f b) -> s -> f t
2022-08-24 14:52:32 +00:00
-- traverseOf :: Functor f => Lens s t a b -> (a -> f b) -> s -> f t
-- traverseOf :: Applicative f => Traversal s t a b -> (a -> f b) -> s -> f t
traverseOf :: Over p f s t a b -> p a (f b) -> s -> f t< / code > < / pre > < / div >
< p class = 'mb-3' >
dafuq?
< / p >
2022-11-23 11:22:29 +00:00
2022-08-24 14:52:32 +00:00
<!-- div class="flex items - center justify - center mt - 2">
< ema:metadata >
< with var = "template" >
< a class = "text-gray-300 hover:text-${theme}-600 text-sm" title = "Edit this page on GitHub"
href="${value:editBaseUrl}/${ema:note:source-path}">
< svg xmlns = "http://www.w3.org/2000/svg" class = "h-6 w-6" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
< / svg >
< / a >
< / with >
< / ema:metadata >
< /div -->
< / article >
< div class = 'flex flex-col lg:flex-row lg:space-x-2' >
< div class = 'flex-1 p-4 mt-8 bg-gray-100 rounded' >
< header class = 'mb-2 text-xl font-semibold text-gray-500' > Links to this page< / header >
< ul class = 'space-y-1' >
< li >
2022-11-23 11:22:29 +00:00
< a class = 'text-purple-600 mavenLinkBold hover:bg-purple-50' href = 'Coding/Haskell/Advantages' >
2022-08-24 14:52:32 +00:00
Talks und Posts zu Haskell
< / a >
< div class = 'mb-4 overflow-auto text-sm text-gray-500' >
2022-08-25 04:18:18 +00:00
< div class = 'pl-2 mt-2 border-l-2 border-purple-200 hover:border-purple-500' >
2022-11-23 11:22:29 +00:00
< div > < a href = 'https://skillsmatter.com/skillscasts/4251-lenses-compositional-data-access-and-manipulation' class = 'text-gray-600 hover:underline' data-linkicon = 'external' target = '_blank' rel = 'noopener' > Lenses< / a > (Registrierung nötig - kostenfrei), siehe auch: < a href = 'Coding/Haskell/Lenses' class = 'text-gray-600 font-bold hover:bg-gray-50' data-wikilink-type = 'WikiLinkBranch' > Lenses< / a > < / div >
2022-08-24 14:52:32 +00:00
< / div >
< / div >
< / li >
< li >
2022-11-23 11:22:29 +00:00
< a class = 'text-purple-600 mavenLinkBold hover:bg-purple-50' href = 'Coding/Haskell/FFPiH' >
2022-08-24 14:52:32 +00:00
Fortgeschrittene funktionale Programmierung in Haskell
< / a >
< div class = 'mb-4 overflow-auto text-sm text-gray-500' >
2022-08-25 04:18:18 +00:00
< div class = 'pl-2 mt-2 border-l-2 border-purple-200 hover:border-purple-500' >
2022-11-23 11:22:29 +00:00
< div > Umschreiben von explizitem Argument-Passing hin zu Monad-Transformers mit stateful < a href = 'Coding/Haskell/Lenses' class = 'text-gray-600 font-bold hover:bg-gray-50' data-wikilink-type = 'WikiLinkBranch' > lenses< / a > < / div >
2022-08-24 14:52:32 +00:00
< / div >
< / div >
< / li >
< / ul >
< / div >
< / div >
< section class = 'flex flex-wrap items-end justify-center my-4 space-x-2 space-y-2 font-mono text-sm' >
< / section >
<!-- What goes in this file will at the very end of the main div -->
< / main >
< / div >
< / div >
< footer class = 'flex items-center justify-center mt-2 mb-8 space-x-4 text-center text-gray-800' >
< div >
< a href = '' title = 'Go to Home page' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-6 h-6 hover:text-purple-700' fill = 'none' viewBox = '0 0 24 24' stroke = 'currentColor' >
2022-08-24 14:52:32 +00:00
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' > < / path >
< / svg >
< / a >
< / div >
< div >
< a href = '-/all' title = 'View Index' >
2022-08-25 04:18:18 +00:00
< svg class = 'w-6 h-6 hover:text-purple-700' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
2022-08-24 14:52:32 +00:00
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4' >
< / path >
< / svg >
< / a >
< / div >
< div >
2022-11-23 11:22:29 +00:00
< a href = 'https://emanote.srid.ca' target = '_blank' title = 'Generated by Emanote 0.8.1.10' >
2022-08-25 04:18:18 +00:00
< img class = 'w-6 h-6 hover:text-purple-700' src = '_emanote-static/emanote-logo.svg' / >
2022-08-24 14:52:32 +00:00
< / a >
< / div >
< div >
< a href = '-/tags' title = 'View tags' >
2022-08-25 04:18:18 +00:00
< svg class = 'w-6 h-6 hover:text-purple-700' fill = 'none' stroke = 'currentColor' viewBox = '0 0 24 24' xmlns = 'http://www.w3.org/2000/svg' >
2022-08-24 14:52:32 +00:00
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z' >
< / path >
< / svg >
< / a >
< / div >
< div >
< a href = '-/tasks' title = 'View tasks' >
2022-08-25 04:18:18 +00:00
< svg xmlns = 'http://www.w3.org/2000/svg' class = 'w-6 h-6 hover:text-purple-700' fill = 'none' viewBox = '0 0 24 24' stroke = 'currentColor' >
2022-08-24 14:52:32 +00:00
< path stroke-linecap = 'round' stroke-linejoin = 'round' stroke-width = '2' d = 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z' > < / path >
< / svg >
< / a >
< / div >
< / footer >
< / div >
< div id = 'stork-search-container' class = 'hidden fixed w-screen h-screen inset-0 backdrop-filter backdrop-blur-sm' >
< div class = 'fixed w-screen h-screen inset-0' onclick = 'window.emanote.stork.toggleSearch()' > < / div >
< div class = 'container mx-auto p-10 mt-10' >
< div class = 'stork-wrapper-flat container mx-auto' >
< input id = 'stork-search-input' data-stork = 'emanote-search' class = 'stork-input' placeholder = 'Search (Ctrl+K) ...' / >
< div data-stork = 'emanote-search-output' class = 'stork-output' > < / div >
< / div >
< / div >
< / div >
2022-11-23 11:22:29 +00:00
2022-08-24 14:52:32 +00:00
< / body >
< / html >