From 32d900eeb671eb8bbfcf62ba2c8c2d6ca8237748 Mon Sep 17 00:00:00 2001 From: NickSeagull Date: Fri, 3 Nov 2023 11:55:02 +0000 Subject: [PATCH] deploy: 84a5a9b14788c0709d863eb7b61ca31f92b01773 --- 404.html | 4 ++-- assets/js/{75f4f5e9.b832dff5.js => 75f4f5e9.03f47b32.js} | 2 +- assets/js/935f2afb.2e7beb8a.js | 1 + assets/js/935f2afb.b334f709.js | 1 - assets/js/9f2b9aaa.0ff5220e.js | 1 + assets/js/9f2b9aaa.4c7b52e8.js | 1 - assets/js/d0609b9f.0a827d4f.js | 1 - assets/js/d0609b9f.5f50e1eb.js | 1 + ...{runtime~main.d8cb77b7.js => runtime~main.d135f9fa.js} | 2 +- blog/0000-index/index.html | 4 ++-- blog/0001-purpose-and-guidelines/index.html | 4 ++-- blog/0002-project-target/index.html | 4 ++-- blog/0003-principles-of-design/index.html | 4 ++-- blog/0004-semantic-versioning/index.html | 4 ++-- blog/0005-better-default-string-type/index.html | 4 ++-- blog/0006-better-default-list-type/index.html | 4 ++-- blog/0007-more-concise-type-signature-syntax/index.html | 4 ++-- blog/archive/index.html | 4 ++-- blog/index.html | 4 ++-- docs/category/extra-topics/index.html | 6 +++--- docs/docs-intro/index.html | 6 +++--- docs/essentials/constants/index.html | 6 +++--- docs/essentials/functions/index.html | 8 ++++---- docs/essentials/laziness/index.html | 6 +++--- docs/essentials/math/index.html | 6 +++--- docs/essentials/types/index.html | 8 ++++---- docs/extra-topics/faq/index.html | 6 +++--- docs/extra-topics/integrating-neohaskell/index.html | 6 +++--- docs/extra-topics/neohaskell-genesis/index.html | 6 +++--- docs/getting-started/dogma/index.html | 6 +++--- docs/getting-started/installing-neohaskell/index.html | 6 +++--- docs/getting-started/interactive-console/index.html | 6 +++--- docs/getting-started/intro/index.html | 6 +++--- index.html | 4 ++-- 34 files changed, 73 insertions(+), 73 deletions(-) rename assets/js/{75f4f5e9.b832dff5.js => 75f4f5e9.03f47b32.js} (70%) create mode 100644 assets/js/935f2afb.2e7beb8a.js delete mode 100644 assets/js/935f2afb.b334f709.js create mode 100644 assets/js/9f2b9aaa.0ff5220e.js delete mode 100644 assets/js/9f2b9aaa.4c7b52e8.js delete mode 100644 assets/js/d0609b9f.0a827d4f.js create mode 100644 assets/js/d0609b9f.5f50e1eb.js rename assets/js/{runtime~main.d8cb77b7.js => runtime~main.d135f9fa.js} (96%) diff --git a/404.html b/404.html index 2fe7e8f..5727db1 100644 --- a/404.html +++ b/404.html @@ -10,13 +10,13 @@ - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/75f4f5e9.b832dff5.js b/assets/js/75f4f5e9.03f47b32.js similarity index 70% rename from assets/js/75f4f5e9.b832dff5.js rename to assets/js/75f4f5e9.03f47b32.js index 619c0b3..9f9dcff 100644 --- a/assets/js/75f4f5e9.b832dff5.js +++ b/assets/js/75f4f5e9.03f47b32.js @@ -1 +1 @@ -"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[6807],{9425:e=>{e.exports=JSON.parse('{"title":"Extra Topics","description":"Here we talk about other topics that are related to NeoHaskell, but are not directly related to helping you understand the language.","slug":"/category/extra-topics","permalink":"/docs/category/extra-topics","navigation":{"previous":{"title":"Wrapper Types","permalink":"/docs/essentials/types"},"next":{"title":"FAQ","permalink":"/docs/extra-topics/faq"}}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[6807],{9425:e=>{e.exports=JSON.parse('{"title":"Extra Topics","description":"Here we talk about other topics that are related to NeoHaskell, but are not directly related to helping you understand the language.","slug":"/category/extra-topics","permalink":"/docs/category/extra-topics","navigation":{"previous":{"title":"Meeting the Types","permalink":"/docs/essentials/types"},"next":{"title":"FAQ","permalink":"/docs/extra-topics/faq"}}}')}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.2e7beb8a.js b/assets/js/935f2afb.2e7beb8a.js new file mode 100644 index 0000000..11af07b --- /dev/null +++ b/assets/js/935f2afb.2e7beb8a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"Note on the Docs","href":"/docs/docs-intro/","docId":"docs-intro/index"},{"type":"category","label":"Getting Started","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"The NeoHaskell Dogma","href":"/docs/getting-started/dogma","docId":"getting-started/dogma"},{"type":"link","label":"Introduction","href":"/docs/getting-started/intro","docId":"getting-started/intro"},{"type":"link","label":"Installing NeoHaskell","href":"/docs/getting-started/installing-neohaskell","docId":"getting-started/installing-neohaskell"},{"type":"link","label":"Playing with the REPL","href":"/docs/getting-started/interactive-console","docId":"getting-started/interactive-console"}]},{"type":"category","label":"Essentials","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Doing Math","href":"/docs/essentials/math","docId":"essentials/math"},{"type":"link","label":"Constants","href":"/docs/essentials/constants","docId":"essentials/constants"},{"type":"link","label":"Laziness","href":"/docs/essentials/laziness","docId":"essentials/laziness"},{"type":"link","label":"Functions","href":"/docs/essentials/functions","docId":"essentials/functions"},{"type":"link","label":"Meeting the Types","href":"/docs/essentials/types","docId":"essentials/types"}]},{"type":"category","label":"Extra Topics","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"FAQ","href":"/docs/extra-topics/faq","docId":"extra-topics/faq"},{"type":"link","label":"The NeoHaskell Genesis","href":"/docs/extra-topics/neohaskell-genesis","docId":"extra-topics/neohaskell-genesis"},{"type":"link","label":"Integrating NeoHaskell","href":"/docs/extra-topics/integrating-neohaskell","docId":"extra-topics/integrating-neohaskell"}],"href":"/docs/category/extra-topics"}]},"docs":{"docs-intro/index":{"id":"docs-intro/index","title":"Note on the Docs","description":"NeoHaskell is an ongoing effort that is yet in an early development stage. Throughout the documentation you might find badges like the following:","sidebar":"tutorialSidebar"},"essentials/constants":{"id":"essentials/constants","title":"Constants","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/functions":{"id":"essentials/functions","title":"Functions","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/laziness":{"id":"essentials/laziness","title":"Laziness","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/math":{"id":"essentials/math","title":"Doing Math","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/types":{"id":"essentials/types","title":"Meeting the Types","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"extra-topics/faq":{"id":"extra-topics/faq","title":"FAQ","description":"This page compiles questions that are commonly asked on Discord.","sidebar":"tutorialSidebar"},"extra-topics/integrating-neohaskell":{"id":"extra-topics/integrating-neohaskell","title":"Integrating NeoHaskell","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"extra-topics/neohaskell-genesis":{"id":"extra-topics/neohaskell-genesis","title":"The NeoHaskell Genesis","description":"The Beginning: Complexity Generation","sidebar":"tutorialSidebar"},"getting-started/dogma":{"id":"getting-started/dogma","title":"The NeoHaskell Dogma","description":"A Dogma is not bad if it is aligned with the principles of the individual, and flexible enough for the moments it isn\'t.","sidebar":"tutorialSidebar"},"getting-started/installing-neohaskell":{"id":"getting-started/installing-neohaskell","title":"Installing NeoHaskell","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"getting-started/interactive-console":{"id":"getting-started/interactive-console","title":"Playing with the REPL","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"getting-started/intro":{"id":"getting-started/intro","title":"Introduction","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.b334f709.js b/assets/js/935f2afb.b334f709.js deleted file mode 100644 index 74d779f..0000000 --- a/assets/js/935f2afb.b334f709.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"Note on the Docs","href":"/docs/docs-intro/","docId":"docs-intro/index"},{"type":"category","label":"Getting Started","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"The NeoHaskell Dogma","href":"/docs/getting-started/dogma","docId":"getting-started/dogma"},{"type":"link","label":"Introduction","href":"/docs/getting-started/intro","docId":"getting-started/intro"},{"type":"link","label":"Installing NeoHaskell","href":"/docs/getting-started/installing-neohaskell","docId":"getting-started/installing-neohaskell"},{"type":"link","label":"Playing with the REPL","href":"/docs/getting-started/interactive-console","docId":"getting-started/interactive-console"}]},{"type":"category","label":"Essentials","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"Doing Math","href":"/docs/essentials/math","docId":"essentials/math"},{"type":"link","label":"Constants","href":"/docs/essentials/constants","docId":"essentials/constants"},{"type":"link","label":"Laziness","href":"/docs/essentials/laziness","docId":"essentials/laziness"},{"type":"link","label":"Functions","href":"/docs/essentials/functions","docId":"essentials/functions"},{"type":"link","label":"Wrapper Types","href":"/docs/essentials/types","docId":"essentials/types"}]},{"type":"category","label":"Extra Topics","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"FAQ","href":"/docs/extra-topics/faq","docId":"extra-topics/faq"},{"type":"link","label":"The NeoHaskell Genesis","href":"/docs/extra-topics/neohaskell-genesis","docId":"extra-topics/neohaskell-genesis"},{"type":"link","label":"Integrating NeoHaskell","href":"/docs/extra-topics/integrating-neohaskell","docId":"extra-topics/integrating-neohaskell"}],"href":"/docs/category/extra-topics"}]},"docs":{"docs-intro/index":{"id":"docs-intro/index","title":"Note on the Docs","description":"NeoHaskell is an ongoing effort that is yet in an early development stage. Throughout the documentation you might find badges like the following:","sidebar":"tutorialSidebar"},"essentials/constants":{"id":"essentials/constants","title":"Constants","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/functions":{"id":"essentials/functions","title":"Functions","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/laziness":{"id":"essentials/laziness","title":"Laziness","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/math":{"id":"essentials/math","title":"Doing Math","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"essentials/types":{"id":"essentials/types","title":"Wrapper Types","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"extra-topics/faq":{"id":"extra-topics/faq","title":"FAQ","description":"This page compiles questions that are commonly asked on Discord.","sidebar":"tutorialSidebar"},"extra-topics/integrating-neohaskell":{"id":"extra-topics/integrating-neohaskell","title":"Integrating NeoHaskell","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"extra-topics/neohaskell-genesis":{"id":"extra-topics/neohaskell-genesis","title":"The NeoHaskell Genesis","description":"The Beginning: Complexity Generation","sidebar":"tutorialSidebar"},"getting-started/dogma":{"id":"getting-started/dogma","title":"The NeoHaskell Dogma","description":"A Dogma is not bad if it is aligned with the principles of the individual, and flexible enough for the moments it isn\'t.","sidebar":"tutorialSidebar"},"getting-started/installing-neohaskell":{"id":"getting-started/installing-neohaskell","title":"Installing NeoHaskell","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"getting-started/interactive-console":{"id":"getting-started/interactive-console","title":"Playing with the REPL","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"},"getting-started/intro":{"id":"getting-started/intro","title":"Introduction","description":"The documentation that you\'re reading is a design document where most of","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/9f2b9aaa.0ff5220e.js b/assets/js/9f2b9aaa.0ff5220e.js new file mode 100644 index 0000000..7685b41 --- /dev/null +++ b/assets/js/9f2b9aaa.0ff5220e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[5957],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>d});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},p=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},h=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,r=e.originalType,s=e.parentName,p=i(e,["components","mdxType","originalType","parentName"]),c=u(n),h=o,d=c["".concat(s,".").concat(h)]||c[h]||m[h]||r;return n?a.createElement(d,l(l({ref:t},p),{},{components:n})):a.createElement(d,l({ref:t},p))}));function d(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var r=n.length,l=new Array(r);l[0]=h;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[c]="string"==typeof e?e:o,l[1]=i;for(var u=2;u{n.d(t,{Z:()=>l});var a=n(7294),o=n(6010);const r={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.Z)(r.tabItem,l),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>N});var a=n(7462),o=n(7294),r=n(6010),l=n(2466),i=n(6550),s=n(1980),u=n(7392),p=n(12);function c(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:o}}=e;return{value:t,label:n,attributes:a,default:o}}))}function m(e){const{values:t,children:n}=e;return(0,o.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function d(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.k6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(r),(0,o.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=m(e),[l,i]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[s,u]=d({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,p.Nk)(n);return[a,(0,o.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),y=(()=>{const e=s??c;return h({value:e,tabValues:r})?e:null})();(0,o.useLayoutEffect)((()=>{y&&i(y)}),[y]);return{selectedValue:l,selectValue:(0,o.useCallback)((e=>{if(!h({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,r]),tabValues:r}}var y=n(2389);const k={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.o5)(),m=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return o.createElement("li",(0,a.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:m},l,{className:(0,r.Z)("tabs__item",k.tabItem,l?.className,{"tabs__item--active":i===t})}),n??t)})))}function b(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=g(e);return o.createElement("div",{className:(0,r.Z)("tabs-container",k.tabList)},o.createElement(f,(0,a.Z)({},e,t)),o.createElement(b,(0,a.Z)({},e,t)))}function N(e){const t=(0,y.Z)();return o.createElement(w,(0,a.Z)({key:String(t)},e))}},165:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);const o=e=>{let{issue:t,absolute:n}=e;const[o,r]=a.useState("loading"),[l,i]=a.useState();(0,a.useEffect)((()=>{try{fetch(`https://api.github.com/repos/neohaskell/neohaskell/issues/${t}`).then((e=>e.json())).then((e=>{e&&(r(e.state),i(e.title))}))}catch(e){console.error(e)}}),[]);const s=o?`NOT IMPLEMENTED YET: #${t} - ${l}`:"LOADING",u=`tooltip ${n?"absolute z-10 p-4":""}`;return a.createElement("a",{className:u,"data-tip":s,target:"_blank",href:`https://github.com/neohaskell/neohaskell/issues/${t}`},a.createElement("div",{className:`badge border-black ${{open:"badge-warning",closed:"hidden",loading:"badge-primary"}[o]} badge-lg`}))}},1645:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);function o(e){let{src:t,sourceLink:n,alt:o}=e;return a.createElement("figure",{className:"!flex !flex-col !place-items-center"},a.createElement("img",{src:t,alt:o}),a.createElement("figcaption",{className:"!text-xs"},`Source: ${n}`))}},5242:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>s,metadata:()=>p,toc:()=>m});var a=n(7462),o=(n(7294),n(3905)),r=n(4866),l=n(5162),i=n(165);n(1645);const s={sidebar_position:4},u="Meeting the Types",p={unversionedId:"essentials/types",id:"essentials/types",title:"Meeting the Types",description:"The documentation that you're reading is a design document where most of",source:"@site/docs/essentials/types.mdx",sourceDirName:"essentials",slug:"/essentials/types",permalink:"/docs/essentials/types",draft:!1,editUrl:"https://github.com/neohaskell/neohaskell.github.io/tree/main/docs/essentials/types.mdx",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Functions",permalink:"/docs/essentials/functions"},next:{title:"Extra Topics",permalink:"/docs/category/extra-topics"}},c={},m=[{value:"Primitive types",id:"primitive-types",level:2},{value:"Annotating Constants",id:"annotating-constants",level:2},{value:"Checking the type of something",id:"checking-the-type-of-something",level:2},{value:"Avoiding primitive obsession",id:"avoiding-primitive-obsession",level:2},{value:"Why do I have to cast it?",id:"why-do-i-have-to-cast-it",level:3},{value:"Annotating Functions",id:"annotating-functions",level:2},{value:"Conclusion",id:"conclusion",level:2}],h={toc:m},d="wrapper";function g(e){let{components:t,...s}=e;return(0,o.kt)(d,(0,a.Z)({},h,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"meeting-the-types"},"Meeting the Types"),(0,o.kt)("admonition",{type:"caution"},(0,o.kt)("p",{parentName:"admonition"},"The documentation that you're reading is a design document where most of\nthe features you're reading are yet to be implemented. Check the ",(0,o.kt)("a",{parentName:"p",href:"/docs/docs-intro"},"Note on the Docs"))),(0,o.kt)("p",null,'One of the key features of NeoHaskell is its type system. Some people have preconceived\nideas about types, and they think that they get in the way of development process too\nmuch. This is usually because either the type system of the programming language they\nare using is not flexible enough, or that types come as an afterthought of the\ndevelopment process of their system, like if it is a "necessary bad thing".'),(0,o.kt)("p",null,"In NeoHaskell, types become your best friends. Not only because NeoHaskell's type\nsystem is quite different to type systems from other programming languages (e.g.\nit doesn't support inheritance, but supports ",(0,o.kt)("em",{parentName:"p"},"super-generics"),") but also because\nit becomes a very useful design tool for your software development toolbox."),(0,o.kt)("p",null,"With the NeoHaskell type system, you can sketch an outline of your system, that then\nyou can fill with the colors of an implementation. If your sketch outlines a dog,\nyou might color it better or worse, but it will still be a dog."),(0,o.kt)("h2",{id:"primitive-types"},"Primitive types"),(0,o.kt)("p",null,"In NeoHaskell, you've got the typical primitive types out of the box:"),(0,o.kt)("table",null,(0,o.kt)("thead",{parentName:"table"},(0,o.kt)("tr",{parentName:"thead"},(0,o.kt)("th",{parentName:"tr",align:null},"Type"),(0,o.kt)("th",{parentName:"tr",align:null},"Description"),(0,o.kt)("th",{parentName:"tr",align:null},"Example"))),(0,o.kt)("tbody",{parentName:"table"},(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Int")),(0,o.kt)("td",{parentName:"tr",align:null},"Integer numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"42"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"BigInt")),(0,o.kt)("td",{parentName:"tr",align:null},"Big integer numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"1234567890123456789012345678901234567890"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Float")),(0,o.kt)("td",{parentName:"tr",align:null},"Simple precision decimal numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"3.1415"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Double")),(0,o.kt)("td",{parentName:"tr",align:null},"Double precision decimal numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"3.141592653589793"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Bool")),(0,o.kt)("td",{parentName:"tr",align:null},"True or False"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"True"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Char")),(0,o.kt)("td",{parentName:"tr",align:null},"Single characters"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"'a'"))))),(0,o.kt)("p",null,"Of course there are much more many types, but you can consider these as the\nmost basic ones."),(0,o.kt)("h2",{id:"annotating-constants"},"Annotating Constants"),(0,o.kt)("p",null,"Until now, when we wanted to create a constant, we just assigned a value to a name.\nIn the NeoHaskell repl, we could do it like so:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> myConstant = 42\n")),(0,o.kt)("p",null,"In this case, the compiler will automatically ",(0,o.kt)("em",{parentName:"p"},"infer")," that the type of ",(0,o.kt)("inlineCode",{parentName:"p"},"myConstant"),"\nis ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),". But it could be possible that we want it to be of type ",(0,o.kt)("inlineCode",{parentName:"p"},"BigInt"),", instead of\n",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"."),(0,o.kt)("p",null,"Given that types are a powerful tool for designing our programs, in NeoHaskell they\nget to be on their own line. This means, that for annotating the type of a constant,\nwe write the type on a line above the assignment line."),(0,o.kt)("p",null,"We write the type of a constant by using the ",(0,o.kt)("inlineCode",{parentName:"p"},"::"),' symbol, which reads as "is of type".'),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"myConstant :: BigInt\nmyConstant = 42\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const myConstant: BigInt = 42;\n// Note, `BigInt` does not actually exist in TypeScript\n")))),(0,o.kt)("admonition",{type:"tip"},(0,o.kt)("p",{parentName:"admonition"},"In the NeoHaskell REPL, use the command ",(0,o.kt)("inlineCode",{parentName:"p"},":{")," to begin writing multiple lines,\nuse the command ",(0,o.kt)("inlineCode",{parentName:"p"},":}")," to end the multi-line input.")),(0,o.kt)("p",null,"If we wanted to try this on the REPL, we could do it like so:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> :{\n myConstant :: BigInt\n myConstant = 42\n :}\n\nneo> myConstant\n42\n")),(0,o.kt)("h2",{id:"checking-the-type-of-something"},"Checking the type of something"),(0,o.kt)("p",null,"In the NeoHaskell REPL, you have a pretty useful command to check the type of stuff,\nyou can write ",(0,o.kt)("inlineCode",{parentName:"p"},":type")," and the name of something, and it will tell you the type:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> :type myConstant\nmyConstant :: BigInt\n")),(0,o.kt)("p",null,'It replies with "myConstant is of type BigInt".'),(0,o.kt)("h2",{id:"avoiding-primitive-obsession"},"Avoiding primitive obsession"),(0,o.kt)("p",null,'There\'s this concept in software development called "primitive obsession" which\nessentially says that using primitive types for everything is bad.'),(0,o.kt)("p",null,"This is because they don't really tell you a story about how your program is\nstructured, or how your data gets transformed."),(0,o.kt)("p",null,'A counter-argument that people usually say against "wrapper types", or types whose\nonly reason of life is to give a name to a primitive type, is that they are not\nvery efficient. You now would need to have both the primitive value in memory, and\nthe wrapper that gives the type a name.'),(0,o.kt)("p",null,"In NeoHaskell, you get to create these wrapper types with no performance penalty."),(0,o.kt)("p",null,"In memory, it is only stored the actual value of the primitive type, but in compilation,\nthe compiler thinks that it is a different type."),(0,o.kt)("p",null,"To create a type of this kind, you use the ",(0,o.kt)("inlineCode",{parentName:"p"},"newtype")," keyword and do it this way:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"newtype Age = Age Int\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},'// Note: In TypeScript there is no such thing as newtype, so the closest thing\n// would be to emulate it with a workaround like the following:\ntype Age = number & { __tag: "Age" };\nconst Age = (age: number): Age => age as Age;\n')))),(0,o.kt)("p",null,"This creates a new type called ",(0,o.kt)("inlineCode",{parentName:"p"},"Age")," whose sole purpose is to be differentiated from\nother ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"s. It also gives us a ",(0,o.kt)("em",{parentName:"p"},"constructor function")," called ",(0,o.kt)("inlineCode",{parentName:"p"},"Age")," that we can use to create a\nnew ",(0,o.kt)("inlineCode",{parentName:"p"},"Age")," value."),(0,o.kt)("p",null,"To create a constant with this type, you use it's ",(0,o.kt)("em",{parentName:"p"},"constructor function")," before the actual ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," value:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"catAge = Age 2\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const catAge = Age(2);\n")))),(0,o.kt)("p",null,"Note how now, you cannot use it as another ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),":"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> catAge + 4\n\n-- TYPE MISMATCH ----------------------------\nAttempting to add two values, but the first value doesn't match the type of the second value:\n\n catAge + 4\n ^^^^^^\n\n`catAge` is of type:\n\n Age\n\nBut `(+)` needs the 1st argument to be:\n\n Int\n\nHint: Maybe try using `cast`?\n")),(0,o.kt)("p",null,"The error message suggests the use of ",(0,o.kt)("inlineCode",{parentName:"p"},"cast")," ",(0,o.kt)(i.Z,{issue:"80",mdxType:"Badge"}),", which is a function that allows you to\ncast a wrapper type to the type that it wraps, with no performance penalty. We can use\nit like so:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> cast catAge + 4\n6\n")),(0,o.kt)("p",null,"In this case, the compiler knows that ",(0,o.kt)("inlineCode",{parentName:"p"},"catAge")," is of type ",(0,o.kt)("inlineCode",{parentName:"p"},"Age"),", and that ",(0,o.kt)("inlineCode",{parentName:"p"},"4")," is of type ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),",\nso it casts ",(0,o.kt)("inlineCode",{parentName:"p"},"catAge")," to ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," and then adds it to ",(0,o.kt)("inlineCode",{parentName:"p"},"4"),"."),(0,o.kt)("h3",{id:"why-do-i-have-to-cast-it"},"Why do I have to cast it?"),(0,o.kt)("p",null,"The reason for wrapper types is that they not only give names to other types, but also that they\ngive you a way to differentiate them from the other types. This is useful for when you want to\ncreate functions that only accept a certain subset of types."),(0,o.kt)("p",null,"It is common that we create a function that accepts many ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"s as its arguments, but we\nwant to differentiate them from each other. For example, we could have a function that\naccepts a ",(0,o.kt)("inlineCode",{parentName:"p"},"weight")," and a ",(0,o.kt)("inlineCode",{parentName:"p"},"distance")," as arguments, and we want to differentiate them from each\nother."),(0,o.kt)("p",null,"Without wrapper types, we could easily mix them up, making the function call to be\nincorrect:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> :{\n calculateEnergy weight distance = ...\n :}\n\nneo> myWeight = 42\nneo> myDistance = 31415\nneo> calculateEnergy myDistance myWeight -- Oops, we mixed them up!\n")),(0,o.kt)("p",null,"To solve this, we could make use of wrapper types, but we also need to know how to specify the types of\nthe arguments of a function. Let's check that out."),(0,o.kt)("h2",{id:"annotating-functions"},"Annotating Functions"),(0,o.kt)("p",null,"Although the compiler doesn't enforce it, you ",(0,o.kt)("strong",{parentName:"p"},"should")," annotate the types of the arguments. Even more,\nthe NeoHaskell good practices recommends that you write the type of the function ",(0,o.kt)("strong",{parentName:"p"},"before")," beggining to\nwrite the function's implementation."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},"The NeoHaskell good practices recommends that you write the type of the function ",(0,o.kt)("strong",{parentName:"p"},"before")," beggining to\nwrite the function's implementation.")),(0,o.kt)("p",null,"This is because, as we mentioned earlier, types are a powerful tool for designing our programs, and\nthey are a great way to communicate the intent of our functions. But this is only true if we ensure that\nwe have good design pieces to work with, like the wrapper types we just saw."),(0,o.kt)("p",null,"To annotate the arguments and result types of a function, we write the types of the arguments separated by\narrows like ",(0,o.kt)("inlineCode",{parentName:"p"},"->"),", being the last one the result type. For example, if we wanted to annotate the\n",(0,o.kt)("inlineCode",{parentName:"p"},"calculateEnergy")," function, we could do it like so:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"calculateEnergy :: Int -> Int -> Int\ncalculateEnergy weight distance = ...\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const calculateEnergy = (weight: number, distance: number): number => ...\n")))),(0,o.kt)("admonition",{type:"note"},(0,o.kt)("p",{parentName:"admonition"},"It is common that newcomers find it weird that there's no separation between the arguments and the result type,\nlike with a parenthesis or something. This is because it is very useful for a thing called ",(0,o.kt)("em",{parentName:"p"},"partial application"),",\nwhich we will see in future sections.")),(0,o.kt)("p",null,"Note how the ",(0,o.kt)("inlineCode",{parentName:"p"},"calculateEnergy")," function accepts two ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"s as arguments, and returns an ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," as a result. But we\nstill have the problem of mixing up the arguments, so let's fix that."),(0,o.kt)("p",null,(0,o.kt)("img",{alt:"types are a lie meme",src:n(4258).Z,width:"882",height:"1200"})),(0,o.kt)("p",null,"A good way of measuring whether a type annotation is good or not, is to check if it is possible to figure out the\nimplementation of the function just by looking at the type annotation. In this case, we can't, because we don't\nknow which argument is which, neither if we need to pass ",(0,o.kt)("inlineCode",{parentName:"p"},"weight"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"distance")," or even maybe some other ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," that\nwe don't know about."),(0,o.kt)("p",null,"Given that weight and distance are two units that probably make sense in our domain, we can create wrapper types\nfor them:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"newtype Weight = Weight Int\nnewtype Distance = Distance Int\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},'type Weight = number & { __tag: "Weight" };\nconst Weight = (weight: number): Weight => weight as Weight;\n\ntype Distance = number & { __tag: "Distance" };\nconst Distance = (distance: number): Distance => distance as Distance;\n')))),(0,o.kt)("p",null,"Now, we can use these wrapper types to annotate the arguments of the ",(0,o.kt)("inlineCode",{parentName:"p"},"calculateEnergy")," function:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"calculateEnergy :: Weight -> Distance -> Int\ncalculateEnergy weight distance = ...\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const calculateEnergy = (weight: Weight, distance: Distance): number => ...\n")))),(0,o.kt)("p",null,"Now it is much better, but we can improve it even more. Is the weight in kilograms? In pounds? In grams? Is the\ndistance in meters? In kilometers? In miles? In light years? We don't know, and we can't know just by looking at\nthe type annotation. Also, we have no clue about the result type, is it in joules? In calories? In kilocalories?"),(0,o.kt)("p",null,"Let's fix that by creating more wrapper types, and changing the type annotation of the function:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"newtype Kilograms = Kilograms Int\nnewtype Meters = Meters Int\nnewtype Joules = Joules Int\n\ncalculateEnergy :: Kilograms -> Meters -> Joules\ncalculateEnergy weight distance = ...\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},'type Kilograms = number & { __tag: "Kilograms" };\nconst Kilograms = (weight: number): Kilograms => weight as Kilograms;\n\ntype Meters = number & { __tag: "Meters" };\nconst Meters = (distance: number): Meters => distance as Meters;\n\ntype Joules = number & { __tag: "Joules" };\nconst Joules = (energy: number): Joules => energy as Joules;\n\nconst calculateEnergy = (weight: Kilograms, distance: Meters): Joules => ...\n')))),(0,o.kt)("p",null,"This is much better, if we take a quick glance at the type annotation, it is very clear what the function does,\nand what it expects as arguments and what it returns as a result."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"calculateEnergy :: Kilograms -> Meters -> Joules\n")),(0,o.kt)("p",null,"We haven't looked at the implementation, and it really doesn't matter, because there's a ",(0,o.kt)("strong",{parentName:"p"},"contract")," that has been\nspecified for this function. It is a contract that we can trust, because it is enforced by the compiler."),(0,o.kt)("p",null,"And remember! Functions in NeoHaskell cannot fail, so if you see a function that returns a ",(0,o.kt)("inlineCode",{parentName:"p"},"Joules"),", you can be\nsure that it will return a ",(0,o.kt)("inlineCode",{parentName:"p"},"Joules")," value ",(0,o.kt)("strong",{parentName:"p"},"always"),", and it will never crash."),(0,o.kt)("p",null,"This is the true power of types, they are a powerful tool for designing our programs, and they are a great way to\nremove the need of having to read the implementation of a function to understand what it does."),(0,o.kt)("h2",{id:"conclusion"},"Conclusion"),(0,o.kt)("p",null,"In this section, we've seen how to annotate the types of constants and functions, and how to create wrapper types\nto avoid primitive obsession."),(0,o.kt)("p",null,"We have dipped our toes into what they call ",(0,o.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain Driven Design"),"\nor DDD, which is the way of designing software that NeoHaskell inspires from."),(0,o.kt)("p",null,"NeoHaskell's way of approaching software development is very different to other programming languages, because it makes\nyou think about the design of your program before you even start writing it. At first, it might seem like a lot of work,\nbut in exchange, you get tools that make your life easier, and your head free of worries."))}g.isMDXComponent=!0},4258:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/types_lie-18c07e96b5c53a03db37a1840ce8ea33.jpeg"}}]); \ No newline at end of file diff --git a/assets/js/9f2b9aaa.4c7b52e8.js b/assets/js/9f2b9aaa.4c7b52e8.js deleted file mode 100644 index 669facf..0000000 --- a/assets/js/9f2b9aaa.4c7b52e8.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[5957],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>d});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},h=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,r=e.originalType,s=e.parentName,u=i(e,["components","mdxType","originalType","parentName"]),c=p(n),h=o,d=c["".concat(s,".").concat(h)]||c[h]||m[h]||r;return n?a.createElement(d,l(l({ref:t},u),{},{components:n})):a.createElement(d,l({ref:t},u))}));function d(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var r=n.length,l=new Array(r);l[0]=h;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[c]="string"==typeof e?e:o,l[1]=i;for(var p=2;p{n.d(t,{Z:()=>l});var a=n(7294),o=n(6010);const r={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.Z)(r.tabItem,l),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>N});var a=n(7462),o=n(7294),r=n(6010),l=n(2466),i=n(6550),s=n(1980),p=n(7392),u=n(12);function c(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:o}}=e;return{value:t,label:n,attributes:a,default:o}}))}function m(e){const{values:t,children:n}=e;return(0,o.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function d(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.k6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(r),(0,o.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=m(e),[l,i]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[s,p]=d({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,u.Nk)(n);return[a,(0,o.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),y=(()=>{const e=s??c;return h({value:e,tabValues:r})?e:null})();(0,o.useLayoutEffect)((()=>{y&&i(y)}),[y]);return{selectedValue:l,selectValue:(0,o.useCallback)((e=>{if(!h({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),g(e)}),[p,g,r]),tabValues:r}}var y=n(2389);const k={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:p}=e;const u=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.o5)(),m=e=>{const t=e.currentTarget,n=u.indexOf(t),a=p[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const n=u.indexOf(e.currentTarget)+1;t=u[n]??u[0];break}case"ArrowLeft":{const n=u.indexOf(e.currentTarget)-1;t=u[n]??u[u.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.Z)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:l}=e;return o.createElement("li",(0,a.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>u.push(e),onKeyDown:h,onClick:m},l,{className:(0,r.Z)("tabs__item",k.tabItem,l?.className,{"tabs__item--active":i===t})}),n??t)})))}function b(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=g(e);return o.createElement("div",{className:(0,r.Z)("tabs-container",k.tabList)},o.createElement(f,(0,a.Z)({},e,t)),o.createElement(b,(0,a.Z)({},e,t)))}function N(e){const t=(0,y.Z)();return o.createElement(w,(0,a.Z)({key:String(t)},e))}},165:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);const o=e=>{let{issue:t,absolute:n}=e;const[o,r]=a.useState("loading"),[l,i]=a.useState();(0,a.useEffect)((()=>{try{fetch(`https://api.github.com/repos/neohaskell/neohaskell/issues/${t}`).then((e=>e.json())).then((e=>{e&&(r(e.state),i(e.title))}))}catch(e){console.error(e)}}),[]);const s=o?`NOT IMPLEMENTED YET: #${t} - ${l}`:"LOADING",p=`tooltip ${n?"absolute z-10 p-4":""}`;return a.createElement("a",{className:p,"data-tip":s,target:"_blank",href:`https://github.com/neohaskell/neohaskell/issues/${t}`},a.createElement("div",{className:`badge border-black ${{open:"badge-warning",closed:"hidden",loading:"badge-primary"}[o]} badge-lg`}))}},1645:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);function o(e){let{src:t,sourceLink:n,alt:o}=e;return a.createElement("figure",{className:"!flex !flex-col !place-items-center"},a.createElement("img",{src:t,alt:o}),a.createElement("figcaption",{className:"!text-xs"},`Source: ${n}`))}},5242:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>u,toc:()=>m});var a=n(7462),o=(n(7294),n(3905)),r=n(4866),l=n(5162),i=n(165);n(1645);const s={sidebar_position:4},p="Wrapper Types",u={unversionedId:"essentials/types",id:"essentials/types",title:"Wrapper Types",description:"The documentation that you're reading is a design document where most of",source:"@site/docs/essentials/types.mdx",sourceDirName:"essentials",slug:"/essentials/types",permalink:"/docs/essentials/types",draft:!1,editUrl:"https://github.com/neohaskell/neohaskell.github.io/tree/main/docs/essentials/types.mdx",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Functions",permalink:"/docs/essentials/functions"},next:{title:"Extra Topics",permalink:"/docs/category/extra-topics"}},c={},m=[{value:"Primitive types",id:"primitive-types",level:2},{value:"Annotating Constants",id:"annotating-constants",level:2},{value:"Checking the type of something",id:"checking-the-type-of-something",level:2},{value:"Avoiding primitive obsession",id:"avoiding-primitive-obsession",level:2},{value:"Why do I have to cast it?",id:"why-do-i-have-to-cast-it",level:3},{value:"Annotating Functions",id:"annotating-functions",level:2},{value:"Conclusion",id:"conclusion",level:2}],h={toc:m},d="wrapper";function g(e){let{components:t,...s}=e;return(0,o.kt)(d,(0,a.Z)({},h,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"wrapper-types"},"Wrapper Types"),(0,o.kt)("admonition",{type:"caution"},(0,o.kt)("p",{parentName:"admonition"},"The documentation that you're reading is a design document where most of\nthe features you're reading are yet to be implemented. Check the ",(0,o.kt)("a",{parentName:"p",href:"/docs/docs-intro"},"Note on the Docs"))),(0,o.kt)("p",null,'One of the key features of NeoHaskell is its type system. Some people have preconceived\nideas about types, and they think that they get in the way of development process too\nmuch. This is usually because either the type system of the programming language they\nare using is not flexible enough, or that types come as an afterthought of the\ndevelopment process of their system, like if it is a "necessary bad thing".'),(0,o.kt)("p",null,"In NeoHaskell, types become your best friends. Not only because NeoHaskell's type\nsystem is quite different to type systems from other programming languages (e.g.\nit doesn't support inheritance, but supports ",(0,o.kt)("em",{parentName:"p"},"super-generics"),") but also because\nit becomes a very useful design tool for your software development toolbox."),(0,o.kt)("p",null,"With the NeoHaskell type system, you can sketch an outline of your system, that then\nyou can fill with the colors of an implementation. If your sketch outlines a dog,\nyou might color it better or worse, but it will still be a dog."),(0,o.kt)("h2",{id:"primitive-types"},"Primitive types"),(0,o.kt)("p",null,"In NeoHaskell, you've got the typical primitive types out of the box:"),(0,o.kt)("table",null,(0,o.kt)("thead",{parentName:"table"},(0,o.kt)("tr",{parentName:"thead"},(0,o.kt)("th",{parentName:"tr",align:null},"Type"),(0,o.kt)("th",{parentName:"tr",align:null},"Description"),(0,o.kt)("th",{parentName:"tr",align:null},"Example"))),(0,o.kt)("tbody",{parentName:"table"},(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Int")),(0,o.kt)("td",{parentName:"tr",align:null},"Integer numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"42"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"BigInt")),(0,o.kt)("td",{parentName:"tr",align:null},"Big integer numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"1234567890123456789012345678901234567890"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Float")),(0,o.kt)("td",{parentName:"tr",align:null},"Simple precision decimal numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"3.1415"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Double")),(0,o.kt)("td",{parentName:"tr",align:null},"Double precision decimal numbers"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"3.141592653589793"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Bool")),(0,o.kt)("td",{parentName:"tr",align:null},"True or False"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"True"))),(0,o.kt)("tr",{parentName:"tbody"},(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"Char")),(0,o.kt)("td",{parentName:"tr",align:null},"Single characters"),(0,o.kt)("td",{parentName:"tr",align:null},(0,o.kt)("inlineCode",{parentName:"td"},"'a'"))))),(0,o.kt)("p",null,"Of course there are much more many types, but you can consider these as the\nmost basic ones."),(0,o.kt)("h2",{id:"annotating-constants"},"Annotating Constants"),(0,o.kt)("p",null,"Until now, when we wanted to create a constant, we just assigned a value to a name.\nIn the NeoHaskell repl, we could do it like so:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> myConstant = 42\n")),(0,o.kt)("p",null,"In this case, the compiler will automatically ",(0,o.kt)("em",{parentName:"p"},"infer")," that the type of ",(0,o.kt)("inlineCode",{parentName:"p"},"myConstant"),"\nis ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),". But it could be possible that we want it to be of type ",(0,o.kt)("inlineCode",{parentName:"p"},"BigInt"),", instead of\n",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"."),(0,o.kt)("p",null,"Given that types are a powerful tool for designing our programs, in NeoHaskell they\nget to be on their own line. This means, that for annotating the type of a constant,\nwe write the type on a line above the assignment line."),(0,o.kt)("p",null,"We write the type of a constant by using the ",(0,o.kt)("inlineCode",{parentName:"p"},"::"),' symbol, which reads as "is of type".'),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"myConstant :: BigInt\nmyConstant = 42\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const myConstant: BigInt = 42;\n// Note, `BigInt` does not actually exist in TypeScript\n")))),(0,o.kt)("admonition",{type:"tip"},(0,o.kt)("p",{parentName:"admonition"},"In the NeoHaskell REPL, use the command ",(0,o.kt)("inlineCode",{parentName:"p"},":{")," to begin writing multiple lines,\nuse the command ",(0,o.kt)("inlineCode",{parentName:"p"},":}")," to end the multi-line input.")),(0,o.kt)("p",null,"If we wanted to try this on the REPL, we could do it like so:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> :{\n myConstant :: BigInt\n myConstant = 42\n :}\n\nneo> myConstant\n42\n")),(0,o.kt)("h2",{id:"checking-the-type-of-something"},"Checking the type of something"),(0,o.kt)("p",null,"In the NeoHaskell REPL, you have a pretty useful command to check the type of stuff,\nyou can write ",(0,o.kt)("inlineCode",{parentName:"p"},":type")," and the name of something, and it will tell you the type:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> :type myConstant\nmyConstant :: BigInt\n")),(0,o.kt)("p",null,'It replies with "myConstant is of type BigInt".'),(0,o.kt)("h2",{id:"avoiding-primitive-obsession"},"Avoiding primitive obsession"),(0,o.kt)("p",null,'There\'s this concept in software development called "primitive obsession" which\nessentially says that using primitive types for everything is bad.'),(0,o.kt)("p",null,"This is because they don't really tell you a story about how your program is\nstructured, or how your data gets transformed."),(0,o.kt)("p",null,'A counter-argument that people usually say against "wrapper types", or types whose\nonly reason of life is to give a name to a primitive type, is that they are not\nvery efficient. You now would need to have both the primitive value in memory, and\nthe wrapper that gives the type a name.'),(0,o.kt)("p",null,"In NeoHaskell, you get to create these wrapper types with no performance penalty."),(0,o.kt)("p",null,"In memory, it is only stored the actual value of the primitive type, but in compilation,\nthe compiler thinks that it is a different type."),(0,o.kt)("p",null,"To create a type of this kind, you use the ",(0,o.kt)("inlineCode",{parentName:"p"},"newtype")," keyword and do it this way:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"newtype Age = Age Int\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},'// Note: In TypeScript there is no such thing as newtype, so the closest thing\n// would be to emulate it with a workaround like the following:\ntype Age = number & { __tag: "Age" };\nconst Age = (age: number): Age => age as Age;\n')))),(0,o.kt)("p",null,"This creates a new type called ",(0,o.kt)("inlineCode",{parentName:"p"},"Age")," whose sole purpose is to be differentiated from\nother ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"s. It also gives us a ",(0,o.kt)("em",{parentName:"p"},"constructor function")," called ",(0,o.kt)("inlineCode",{parentName:"p"},"Age")," that we can use to create a\nnew ",(0,o.kt)("inlineCode",{parentName:"p"},"Age")," value."),(0,o.kt)("p",null,"To create a constant with this type, you use it's ",(0,o.kt)("em",{parentName:"p"},"constructor function")," before the actual ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," value:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"catAge = Age 2\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const catAge = Age(2);\n")))),(0,o.kt)("p",null,"Note how now, you cannot use it as another ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),":"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> catAge + 4\n\n-- TYPE MISMATCH ----------------------------\nAttempting to add two values, but the first value doesn't match the type of the second value:\n\n catAge + 4\n ^^^^^^\n\n`catAge` is of type:\n\n Age\n\nBut `(+)` needs the 1st argument to be:\n\n Int\n\nHint: Maybe try using `cast`?\n")),(0,o.kt)("p",null,"The error message suggests the use of ",(0,o.kt)("inlineCode",{parentName:"p"},"cast")," ",(0,o.kt)(i.Z,{issue:"80",mdxType:"Badge"}),", which is a function that allows you to\ncast a wrapper type to the type that it wraps, with no performance penalty. We can use\nit like so:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> cast catAge + 4\n6\n")),(0,o.kt)("p",null,"In this case, the compiler knows that ",(0,o.kt)("inlineCode",{parentName:"p"},"catAge")," is of type ",(0,o.kt)("inlineCode",{parentName:"p"},"Age"),", and that ",(0,o.kt)("inlineCode",{parentName:"p"},"4")," is of type ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),",\nso it casts ",(0,o.kt)("inlineCode",{parentName:"p"},"catAge")," to ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," and then adds it to ",(0,o.kt)("inlineCode",{parentName:"p"},"4"),"."),(0,o.kt)("h3",{id:"why-do-i-have-to-cast-it"},"Why do I have to cast it?"),(0,o.kt)("p",null,"The reason for wrapper types is that they not only give names to other types, but also that they\ngive you a way to differentiate them from the other types. This is useful for when you want to\ncreate functions that only accept a certain subset of types."),(0,o.kt)("p",null,"It is common that we create a function that accepts many ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"s as its arguments, but we\nwant to differentiate them from each other. For example, we could have a function that\naccepts a ",(0,o.kt)("inlineCode",{parentName:"p"},"weight")," and a ",(0,o.kt)("inlineCode",{parentName:"p"},"distance")," as arguments, and we want to differentiate them from each\nother."),(0,o.kt)("p",null,"Without wrapper types, we could easily mix them up, making the function call to be\nincorrect:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"neo> :{\n calculateEnergy weight distance = ...\n :}\n\nneo> myWeight = 42\nneo> myDistance = 31415\nneo> calculateEnergy myDistance myWeight -- Oops, we mixed them up!\n")),(0,o.kt)("p",null,"To solve this, we could make use of wrapper types, but we also need to know how to specify the types of\nthe arguments of a function. Let's check that out."),(0,o.kt)("h2",{id:"annotating-functions"},"Annotating Functions"),(0,o.kt)("p",null,"Although the compiler doesn't enforce it, you ",(0,o.kt)("strong",{parentName:"p"},"should")," annotate the types of the arguments. Even more,\nthe NeoHaskell good practices recommends that you write the type of the function ",(0,o.kt)("strong",{parentName:"p"},"before")," beggining to\nwrite the function's implementation."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},"The NeoHaskell good practices recommends that you write the type of the function ",(0,o.kt)("strong",{parentName:"p"},"before")," beggining to\nwrite the function's implementation.")),(0,o.kt)("p",null,"This is because, as we mentioned earlier, types are a powerful tool for designing our programs, and\nthey are a great way to communicate the intent of our functions. But this is only true if we ensure that\nwe have good design pieces to work with, like the wrapper types we just saw."),(0,o.kt)("p",null,"To annotate the arguments and result types of a function, we write the types of the arguments separated by\narrows like ",(0,o.kt)("inlineCode",{parentName:"p"},"->"),", being the last one the result type. For example, if we wanted to annotate the\n",(0,o.kt)("inlineCode",{parentName:"p"},"calculateEnergy")," function, we could do it like so:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"calculateEnergy :: Int -> Int -> Int\ncalculateEnergy weight distance = ...\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const calculateEnergy = (weight: number, distance: number): number => ...\n")))),(0,o.kt)("admonition",{type:"note"},(0,o.kt)("p",{parentName:"admonition"},"It is common that newcomers find it weird that there's no separation between the arguments and the result type,\nlike with a parenthesis or something. This is because it is very useful for a thing called ",(0,o.kt)("em",{parentName:"p"},"partial application"),",\nwhich we will see in future sections.")),(0,o.kt)("p",null,"Note how the ",(0,o.kt)("inlineCode",{parentName:"p"},"calculateEnergy")," function accepts two ",(0,o.kt)("inlineCode",{parentName:"p"},"Int"),"s as arguments, and returns an ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," as a result. But we\nstill have the problem of mixing up the arguments, so let's fix that."),(0,o.kt)("p",null,(0,o.kt)("img",{alt:"types are a lie meme",src:n(4258).Z,width:"882",height:"1200"})),(0,o.kt)("p",null,"A good way of measuring whether a type annotation is good or not, is to check if it is possible to figure out the\nimplementation of the function just by looking at the type annotation. In this case, we can't, because we don't\nknow which argument is which, neither if we need to pass ",(0,o.kt)("inlineCode",{parentName:"p"},"weight"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"distance")," or even maybe some other ",(0,o.kt)("inlineCode",{parentName:"p"},"Int")," that\nwe don't know about."),(0,o.kt)("p",null,"Given that weight and distance are two units that probably make sense in our domain, we can create wrapper types\nfor them:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"newtype Weight = Weight Int\nnewtype Distance = Distance Int\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},'type Weight = number & { __tag: "Weight" };\nconst Weight = (weight: number): Weight => weight as Weight;\n\ntype Distance = number & { __tag: "Distance" };\nconst Distance = (distance: number): Distance => distance as Distance;\n')))),(0,o.kt)("p",null,"Now, we can use these wrapper types to annotate the arguments of the ",(0,o.kt)("inlineCode",{parentName:"p"},"calculateEnergy")," function:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"calculateEnergy :: Weight -> Distance -> Int\ncalculateEnergy weight distance = ...\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},"const calculateEnergy = (weight: Weight, distance: Distance): number => ...\n")))),(0,o.kt)("p",null,"Now it is much better, but we can improve it even more. Is the weight in kilograms? In pounds? In grams? Is the\ndistance in meters? In kilometers? In miles? In light years? We don't know, and we can't know just by looking at\nthe type annotation. Also, we have no clue about the result type, is it in joules? In calories? In kilocalories?"),(0,o.kt)("p",null,"Let's fix that by creating more wrapper types, and changing the type annotation of the function:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(l.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"newtype Kilograms = Kilograms Int\nnewtype Meters = Meters Int\nnewtype Joules = Joules Int\n\ncalculateEnergy :: Kilograms -> Meters -> Joules\ncalculateEnergy weight distance = ...\n"))),(0,o.kt)(l.Z,{value:"ts",label:"TypeScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-typescript"},'type Kilograms = number & { __tag: "Kilograms" };\nconst Kilograms = (weight: number): Kilograms => weight as Kilograms;\n\ntype Meters = number & { __tag: "Meters" };\nconst Meters = (distance: number): Meters => distance as Meters;\n\ntype Joules = number & { __tag: "Joules" };\nconst Joules = (energy: number): Joules => energy as Joules;\n\nconst calculateEnergy = (weight: Kilograms, distance: Meters): Joules => ...\n')))),(0,o.kt)("p",null,"This is much better, if we take a quick glance at the type annotation, it is very clear what the function does,\nand what it expects as arguments and what it returns as a result."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"calculateEnergy :: Kilograms -> Meters -> Joules\n")),(0,o.kt)("p",null,"We haven't looked at the implementation, and it really doesn't matter, because there's a ",(0,o.kt)("strong",{parentName:"p"},"contract")," that has been\nspecified for this function. It is a contract that we can trust, because it is enforced by the compiler."),(0,o.kt)("p",null,"And remember! Functions in NeoHaskell cannot fail, so if you see a function that returns a ",(0,o.kt)("inlineCode",{parentName:"p"},"Joules"),", you can be\nsure that it will return a ",(0,o.kt)("inlineCode",{parentName:"p"},"Joules")," value ",(0,o.kt)("strong",{parentName:"p"},"always"),", and it will never crash."),(0,o.kt)("p",null,"This is the true power of types, they are a powerful tool for designing our programs, and they are a great way to\nremove the need of having to read the implementation of a function to understand what it does."),(0,o.kt)("h2",{id:"conclusion"},"Conclusion"),(0,o.kt)("p",null,"In this section, we've seen how to annotate the types of constants and functions, and how to create wrapper types\nto avoid primitive obsession."),(0,o.kt)("p",null,"We have dipped our toes into what they call ",(0,o.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain Driven Design"),"\nor DDD, which is the way of designing software that NeoHaskell inspires from."),(0,o.kt)("p",null,"NeoHaskell's way of approaching software development is very different to other programming languages, because it makes\nyou think about the design of your program before you even start writing it. At first, it might seem like a lot of work,\nbut in exchange, you get tools that make your life easier, and your head free of worries."))}g.isMDXComponent=!0},4258:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/types_lie-18c07e96b5c53a03db37a1840ce8ea33.jpeg"}}]); \ No newline at end of file diff --git a/assets/js/d0609b9f.0a827d4f.js b/assets/js/d0609b9f.0a827d4f.js deleted file mode 100644 index ce9dd55..0000000 --- a/assets/js/d0609b9f.0a827d4f.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[8794],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>h});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=u(n),d=o,h=p["".concat(s,".").concat(d)]||p[d]||m[d]||r;return n?a.createElement(h,i(i({ref:t},c),{},{components:n})):a.createElement(h,i({ref:t},c))}));function h(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var r=n.length,i=new Array(r);i[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:o,i[1]=l;for(var u=2;u{n.d(t,{Z:()=>i});var a=n(7294),o=n(6010);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.Z)(r.tabItem,i),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>v});var a=n(7462),o=n(7294),r=n(6010),i=n(2466),l=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:o}}=e;return{value:t,label:n,attributes:a,default:o}}))}function m(e){const{values:t,children:n}=e;return(0,o.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.k6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(r),(0,o.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=m(e),[i,l]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[s,u]=h({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,c.Nk)(n);return[a,(0,o.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),g=(()=>{const e=s??p;return d({value:e,tabValues:r})?e:null})();(0,o.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,o.useCallback)((e=>{if(!d({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,r]),tabValues:r}}var g=n(2389);const k={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.o5)(),m=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},d=e=>{let t=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return o.createElement("li",(0,a.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:m},i,{className:(0,r.Z)("tabs__item",k.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function y(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return o.createElement("div",{className:(0,r.Z)("tabs-container",k.tabList)},o.createElement(b,(0,a.Z)({},e,t)),o.createElement(y,(0,a.Z)({},e,t)))}function v(e){const t=(0,g.Z)();return o.createElement(w,(0,a.Z)({key:String(t)},e))}},165:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);const o=e=>{let{issue:t,absolute:n}=e;const[o,r]=a.useState("loading"),[i,l]=a.useState();(0,a.useEffect)((()=>{try{fetch(`https://api.github.com/repos/neohaskell/neohaskell/issues/${t}`).then((e=>e.json())).then((e=>{e&&(r(e.state),l(e.title))}))}catch(e){console.error(e)}}),[]);const s=o?`NOT IMPLEMENTED YET: #${t} - ${i}`:"LOADING",u=`tooltip ${n?"absolute z-10 p-4":""}`;return a.createElement("a",{className:u,"data-tip":s,target:"_blank",href:`https://github.com/neohaskell/neohaskell/issues/${t}`},a.createElement("div",{className:`badge border-black ${{open:"badge-warning",closed:"hidden",loading:"badge-primary"}[o]} badge-lg`}))}},1645:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);function o(e){let{src:t,sourceLink:n,alt:o}=e;return a.createElement("figure",{className:"!flex !flex-col !place-items-center"},a.createElement("img",{src:t,alt:o}),a.createElement("figcaption",{className:"!text-xs"},`Source: ${n}`))}},3441:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(7462),o=(n(7294),n(3905)),r=n(4866),i=n(5162);n(165),n(1645);const l={sidebar_position:3},s="Functions",u={unversionedId:"essentials/functions",id:"essentials/functions",title:"Functions",description:"The documentation that you're reading is a design document where most of",source:"@site/docs/essentials/functions.mdx",sourceDirName:"essentials",slug:"/essentials/functions",permalink:"/docs/essentials/functions",draft:!1,editUrl:"https://github.com/neohaskell/neohaskell.github.io/tree/main/docs/essentials/functions.mdx",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Laziness",permalink:"/docs/essentials/laziness"},next:{title:"Wrapper Types",permalink:"/docs/essentials/types"}},c={},p=[{value:"Calling a Function",id:"calling-a-function",level:2},{value:"Defining Functions",id:"defining-functions",level:2},{value:"A Note on Cognitive Overhead",id:"a-note-on-cognitive-overhead",level:2}],m={toc:p},d="wrapper";function h(e){let{components:t,...n}=e;return(0,o.kt)(d,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"functions"},"Functions"),(0,o.kt)("admonition",{type:"caution"},(0,o.kt)("p",{parentName:"admonition"},"The documentation that you're reading is a design document where most of\nthe features you're reading are yet to be implemented. Check the ",(0,o.kt)("a",{parentName:"p",href:"/docs/docs-intro"},"Note on the Docs"))),(0,o.kt)("p",null,"NeoHaskell promotes the style of programming that's called ",(0,o.kt)("strong",{parentName:"p"},"functional programming"),". There are so many definitions of\nfunctional programming, but it boils down to one thing: ",(0,o.kt)("strong",{parentName:"p"},"programming with functions"),"."),(0,o.kt)("p",null,"Functions can be divided in two types: ",(0,o.kt)("strong",{parentName:"p"},"Calculations")," and ",(0,o.kt)("strong",{parentName:"p"},"Actions"),"."),(0,o.kt)("p",null,"A ",(0,o.kt)("strong",{parentName:"p"},"calculation")," is a function that will always return the same result for the same input. ",(0,o.kt)("strong",{parentName:"p"},"In NeoHaskell, they cannot crash/throw an error"),". Examples of calculations are:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Performing mathematical operations (adding, subtracting, etc)"),(0,o.kt)("li",{parentName:"ul"},"Returning the length of a list"),(0,o.kt)("li",{parentName:"ul"},"Converting one object to another"),(0,o.kt)("li",{parentName:"ul"},"Doing operations with strings")),(0,o.kt)("p",null,"An ",(0,o.kt)("strong",{parentName:"p"},"action")," is a function that usually relies on some external thing to perform its job. ",(0,o.kt)("strong",{parentName:"p"},"These can throw errors.")," Examples of actions are:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Reading a file from the disk"),(0,o.kt)("li",{parentName:"ul"},"Sending an email"),(0,o.kt)("li",{parentName:"ul"},"Modifying some data in the state of the application"),(0,o.kt)("li",{parentName:"ul"},"Printing something to the screen")),(0,o.kt)("p",null,"This distinction is crucial, because when looking for a crash in your application, you can be sure that the problem is\nin an action."),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"In NeoHaskell, by default all functions are calculations"),". The compiler won't let you perform an action without explicitly\ntelling it that you're doing so. We will cover actions in later sections, so for now, just know ",(0,o.kt)("strong",{parentName:"p"},"that all functions you see are calculations, and cannot fail/crash.")),(0,o.kt)("h2",{id:"calling-a-function"},"Calling a Function"),(0,o.kt)("p",null,"In NeoHaskell, to call a function, you ",(0,o.kt)("strong",{parentName:"p"},"write its name and then the arguments separated by spaces"),". This usually baffles people coming from other languages, but it helps you write more readable and beautiful code."),(0,o.kt)("p",null,"Suppose we want to call a function called ",(0,o.kt)("inlineCode",{parentName:"p"},"estimateShipping")," that returns the cost based on weight in kilos, distance in kilometers, and cost per kilometer. Here's how you would do it. Compare the NeoHaskell code, and the JavaScript code (click on the tabs to switch languages):"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping 15 100 2\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-python"},"estimateShipping(15, 100, 2)\n")))),(0,o.kt)("p",null,"The only time that we use parentheses is when we want to pack another function call or using an operator as an argument. For example, if we wanted to call ",(0,o.kt)("inlineCode",{parentName:"p"},"estimateShipping")," adding the weights as the first argument, and with the result of ",(0,o.kt)("inlineCode",{parentName:"p"},"calculateDistance")," as the second argument, we would do it like this:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping (7 + 8) (calculateDistance 10 20) 2\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-python"},"estimateShipping(7 + 8, calculateDistance(10, 20), 2)\n")))),(0,o.kt)("p",null,"At this point, it is definitely not clear that one style is more beautiful than another. But it is because that also it is\nadvised to extract the arguments to constants, and then call the function with the constants, inside of a function that you've\ndefined."),(0,o.kt)("h2",{id:"defining-functions"},"Defining Functions"),(0,o.kt)("p",null,"To define a function in NeoHaskell, you write the name of the function, followed by the arguments, and then an equal sign, and then a block for the body of the function. This is too much information to process in a sentence, so let's see an example."),(0,o.kt)("p",null,"Let's write a function that calculates the shipping cost, given the weight, distance, and cost per kilometer:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping weight distance costPerKm = do\n let distanceCost = distance * costPerKm\n let weightCost = weight * 2\n distanceCost + weightCost\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-js"},"function estimateShipping(weight, distance, costPerKm) {\n const distanceCost = distance * costPerKm;\n const weightCost = weight * 2;\n return distanceCost + weightCost;\n}\n")))),(0,o.kt)("p",null,"In the example above, we're using a ",(0,o.kt)("strong",{parentName:"p"},"block")," to define a function, so it allows us to define constants with the ",(0,o.kt)("inlineCode",{parentName:"p"},"let")," keyword. Note how that the last line in a block is\nreturned, so there's no need to write ",(0,o.kt)("inlineCode",{parentName:"p"},"return")," like in\nother languages."),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Blocks are optional"),", and if we wanted to write the same function without it, removing all the constants,\nand writing the calculation inline, we could do it like this:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping weight distance costPerKm =\n distance * costPerKm + weight * 2\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-js"},"function estimateShipping(weight, distance, costPerKm) {\n return distance * costPerKm + weight * 2;\n}\n")))),(0,o.kt)("p",null,"This way of writing code helps writing data processing code and validation rules in a very readable way. ",(0,o.kt)("strong",{parentName:"p"},"But be careful"),",\nbecause one-liners can easily become hard to read, so ",(0,o.kt)("strong",{parentName:"p"},"it is advised to use blocks for more complex functions.")),(0,o.kt)("h2",{id:"a-note-on-cognitive-overhead"},"A Note on Cognitive Overhead"),(0,o.kt)("p",null,"In the function we've defined above, we're naming our arguments ",(0,o.kt)("inlineCode",{parentName:"p"},"weight"),", and ",(0,o.kt)("inlineCode",{parentName:"p"},"distance"),". Is the weight in kilograms or pounds? Is the distance in kilometers, miles, ",(0,o.kt)("a",{parentName:"p",href:"https://edition.cnn.com/2020/04/04/us/social-distancing-florida-alligator-trnd/index.html"},"or alligators"),"? We don't know, and we might have to look at the implementation of the function to know. And still, we'd have no clue, because there aren't even comments in the function."),(0,o.kt)("p",null,"This is extra work that our brains must have to do, and when you're trying to navigate a codebase with hundreds (or thousands) of functions, it can become a nightmare. These kinds of issues might seem anecdotic, but actually ",(0,o.kt)("a",{parentName:"p",href:"https://www.simscale.com/blog/nasa-mars-climate-orbiter-metric/"},"the NASA lost millions due to a similar situation"),"."),(0,o.kt)("p",null,"NeoHaskell takes an approach that helps you define explicit contracts for all of the functions. We do so by ",(0,o.kt)("strong",{parentName:"p"},"modeling our domain")," (the problem we're trying to solve) ",(0,o.kt)("strong",{parentName:"p"},"with types"),"."),(0,o.kt)("p",null,"Let's hop into the next section to learn more about them!"))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d0609b9f.5f50e1eb.js b/assets/js/d0609b9f.5f50e1eb.js new file mode 100644 index 0000000..4a50d9c --- /dev/null +++ b/assets/js/d0609b9f.5f50e1eb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkneohaskell_github_io=self.webpackChunkneohaskell_github_io||[]).push([[8794],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>h});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=u(n),d=o,h=p["".concat(s,".").concat(d)]||p[d]||m[d]||r;return n?a.createElement(h,i(i({ref:t},c),{},{components:n})):a.createElement(h,i({ref:t},c))}));function h(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var r=n.length,i=new Array(r);i[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:o,i[1]=l;for(var u=2;u{n.d(t,{Z:()=>i});var a=n(7294),o=n(6010);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.Z)(r.tabItem,i),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>v});var a=n(7462),o=n(7294),r=n(6010),i=n(2466),l=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:o}}=e;return{value:t,label:n,attributes:a,default:o}}))}function m(e){const{values:t,children:n}=e;return(0,o.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.k6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(r),(0,o.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=m(e),[i,l]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[s,u]=h({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,c.Nk)(n);return[a,(0,o.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),g=(()=>{const e=s??p;return d({value:e,tabValues:r})?e:null})();(0,o.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,o.useCallback)((e=>{if(!d({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,r]),tabValues:r}}var g=n(2389);const k={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.o5)(),m=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},d=e=>{let t=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return o.createElement("li",(0,a.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:m},i,{className:(0,r.Z)("tabs__item",k.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function y(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return o.createElement("div",{className:(0,r.Z)("tabs-container",k.tabList)},o.createElement(b,(0,a.Z)({},e,t)),o.createElement(y,(0,a.Z)({},e,t)))}function v(e){const t=(0,g.Z)();return o.createElement(w,(0,a.Z)({key:String(t)},e))}},165:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);const o=e=>{let{issue:t,absolute:n}=e;const[o,r]=a.useState("loading"),[i,l]=a.useState();(0,a.useEffect)((()=>{try{fetch(`https://api.github.com/repos/neohaskell/neohaskell/issues/${t}`).then((e=>e.json())).then((e=>{e&&(r(e.state),l(e.title))}))}catch(e){console.error(e)}}),[]);const s=o?`NOT IMPLEMENTED YET: #${t} - ${i}`:"LOADING",u=`tooltip ${n?"absolute z-10 p-4":""}`;return a.createElement("a",{className:u,"data-tip":s,target:"_blank",href:`https://github.com/neohaskell/neohaskell/issues/${t}`},a.createElement("div",{className:`badge border-black ${{open:"badge-warning",closed:"hidden",loading:"badge-primary"}[o]} badge-lg`}))}},1645:(e,t,n)=>{n.d(t,{Z:()=>o});var a=n(7294);function o(e){let{src:t,sourceLink:n,alt:o}=e;return a.createElement("figure",{className:"!flex !flex-col !place-items-center"},a.createElement("img",{src:t,alt:o}),a.createElement("figcaption",{className:"!text-xs"},`Source: ${n}`))}},3441:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(7462),o=(n(7294),n(3905)),r=n(4866),i=n(5162);n(165),n(1645);const l={sidebar_position:3},s="Functions",u={unversionedId:"essentials/functions",id:"essentials/functions",title:"Functions",description:"The documentation that you're reading is a design document where most of",source:"@site/docs/essentials/functions.mdx",sourceDirName:"essentials",slug:"/essentials/functions",permalink:"/docs/essentials/functions",draft:!1,editUrl:"https://github.com/neohaskell/neohaskell.github.io/tree/main/docs/essentials/functions.mdx",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Laziness",permalink:"/docs/essentials/laziness"},next:{title:"Meeting the Types",permalink:"/docs/essentials/types"}},c={},p=[{value:"Calling a Function",id:"calling-a-function",level:2},{value:"Defining Functions",id:"defining-functions",level:2},{value:"A Note on Cognitive Overhead",id:"a-note-on-cognitive-overhead",level:2}],m={toc:p},d="wrapper";function h(e){let{components:t,...n}=e;return(0,o.kt)(d,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"functions"},"Functions"),(0,o.kt)("admonition",{type:"caution"},(0,o.kt)("p",{parentName:"admonition"},"The documentation that you're reading is a design document where most of\nthe features you're reading are yet to be implemented. Check the ",(0,o.kt)("a",{parentName:"p",href:"/docs/docs-intro"},"Note on the Docs"))),(0,o.kt)("p",null,"NeoHaskell promotes the style of programming that's called ",(0,o.kt)("strong",{parentName:"p"},"functional programming"),". There are so many definitions of\nfunctional programming, but it boils down to one thing: ",(0,o.kt)("strong",{parentName:"p"},"programming with functions"),"."),(0,o.kt)("p",null,"Functions can be divided in two types: ",(0,o.kt)("strong",{parentName:"p"},"Calculations")," and ",(0,o.kt)("strong",{parentName:"p"},"Actions"),"."),(0,o.kt)("p",null,"A ",(0,o.kt)("strong",{parentName:"p"},"calculation")," is a function that will always return the same result for the same input. ",(0,o.kt)("strong",{parentName:"p"},"In NeoHaskell, they cannot crash/throw an error"),". Examples of calculations are:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Performing mathematical operations (adding, subtracting, etc)"),(0,o.kt)("li",{parentName:"ul"},"Returning the length of a list"),(0,o.kt)("li",{parentName:"ul"},"Converting one object to another"),(0,o.kt)("li",{parentName:"ul"},"Doing operations with strings")),(0,o.kt)("p",null,"An ",(0,o.kt)("strong",{parentName:"p"},"action")," is a function that usually relies on some external thing to perform its job. ",(0,o.kt)("strong",{parentName:"p"},"These can throw errors.")," Examples of actions are:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Reading a file from the disk"),(0,o.kt)("li",{parentName:"ul"},"Sending an email"),(0,o.kt)("li",{parentName:"ul"},"Modifying some data in the state of the application"),(0,o.kt)("li",{parentName:"ul"},"Printing something to the screen")),(0,o.kt)("p",null,"This distinction is crucial, because when looking for a crash in your application, you can be sure that the problem is\nin an action."),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"In NeoHaskell, by default all functions are calculations"),". The compiler won't let you perform an action without explicitly\ntelling it that you're doing so. We will cover actions in later sections, so for now, just know ",(0,o.kt)("strong",{parentName:"p"},"that all functions you see are calculations, and cannot fail/crash.")),(0,o.kt)("h2",{id:"calling-a-function"},"Calling a Function"),(0,o.kt)("p",null,"In NeoHaskell, to call a function, you ",(0,o.kt)("strong",{parentName:"p"},"write its name and then the arguments separated by spaces"),". This usually baffles people coming from other languages, but it helps you write more readable and beautiful code."),(0,o.kt)("p",null,"Suppose we want to call a function called ",(0,o.kt)("inlineCode",{parentName:"p"},"estimateShipping")," that returns the cost based on weight in kilos, distance in kilometers, and cost per kilometer. Here's how you would do it. Compare the NeoHaskell code, and the JavaScript code (click on the tabs to switch languages):"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping 15 100 2\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-python"},"estimateShipping(15, 100, 2)\n")))),(0,o.kt)("p",null,"The only time that we use parentheses is when we want to pack another function call or using an operator as an argument. For example, if we wanted to call ",(0,o.kt)("inlineCode",{parentName:"p"},"estimateShipping")," adding the weights as the first argument, and with the result of ",(0,o.kt)("inlineCode",{parentName:"p"},"calculateDistance")," as the second argument, we would do it like this:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping (7 + 8) (calculateDistance 10 20) 2\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-python"},"estimateShipping(7 + 8, calculateDistance(10, 20), 2)\n")))),(0,o.kt)("p",null,"At this point, it is definitely not clear that one style is more beautiful than another. But it is because that also it is\nadvised to extract the arguments to constants, and then call the function with the constants, inside of a function that you've\ndefined."),(0,o.kt)("h2",{id:"defining-functions"},"Defining Functions"),(0,o.kt)("p",null,"To define a function in NeoHaskell, you write the name of the function, followed by the arguments, and then an equal sign, and then a block for the body of the function. This is too much information to process in a sentence, so let's see an example."),(0,o.kt)("p",null,"Let's write a function that calculates the shipping cost, given the weight, distance, and cost per kilometer:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping weight distance costPerKm = do\n let distanceCost = distance * costPerKm\n let weightCost = weight * 2\n distanceCost + weightCost\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-js"},"function estimateShipping(weight, distance, costPerKm) {\n const distanceCost = distance * costPerKm;\n const weightCost = weight * 2;\n return distanceCost + weightCost;\n}\n")))),(0,o.kt)("p",null,"In the example above, we're using a ",(0,o.kt)("strong",{parentName:"p"},"block")," to define a function, so it allows us to define constants with the ",(0,o.kt)("inlineCode",{parentName:"p"},"let")," keyword. Note how that the last line in a block is\nreturned, so there's no need to write ",(0,o.kt)("inlineCode",{parentName:"p"},"return")," like in\nother languages."),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Blocks are optional"),", and if we wanted to write the same function without it, removing all the constants,\nand writing the calculation inline, we could do it like this:"),(0,o.kt)(r.Z,{mdxType:"Tabs"},(0,o.kt)(i.Z,{value:"neohaskell",label:"NeoHaskell",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-haskell"},"estimateShipping weight distance costPerKm =\n distance * costPerKm + weight * 2\n"))),(0,o.kt)(i.Z,{value:"js",label:"JavaScript",mdxType:"TabItem"},(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-js"},"function estimateShipping(weight, distance, costPerKm) {\n return distance * costPerKm + weight * 2;\n}\n")))),(0,o.kt)("p",null,"This way of writing code helps writing data processing code and validation rules in a very readable way. ",(0,o.kt)("strong",{parentName:"p"},"But be careful"),",\nbecause one-liners can easily become hard to read, so ",(0,o.kt)("strong",{parentName:"p"},"it is advised to use blocks for more complex functions.")),(0,o.kt)("h2",{id:"a-note-on-cognitive-overhead"},"A Note on Cognitive Overhead"),(0,o.kt)("p",null,"In the function we've defined above, we're naming our arguments ",(0,o.kt)("inlineCode",{parentName:"p"},"weight"),", and ",(0,o.kt)("inlineCode",{parentName:"p"},"distance"),". Is the weight in kilograms or pounds? Is the distance in kilometers, miles, ",(0,o.kt)("a",{parentName:"p",href:"https://edition.cnn.com/2020/04/04/us/social-distancing-florida-alligator-trnd/index.html"},"or alligators"),"? We don't know, and we might have to look at the implementation of the function to know. And still, we'd have no clue, because there aren't even comments in the function."),(0,o.kt)("p",null,"This is extra work that our brains must have to do, and when you're trying to navigate a codebase with hundreds (or thousands) of functions, it can become a nightmare. These kinds of issues might seem anecdotic, but actually ",(0,o.kt)("a",{parentName:"p",href:"https://www.simscale.com/blog/nasa-mars-climate-orbiter-metric/"},"the NASA lost millions due to a similar situation"),"."),(0,o.kt)("p",null,"NeoHaskell takes an approach that helps you define explicit contracts for all of the functions. We do so by ",(0,o.kt)("strong",{parentName:"p"},"modeling our domain")," (the problem we're trying to solve) ",(0,o.kt)("strong",{parentName:"p"},"with types"),"."),(0,o.kt)("p",null,"Let's hop into the next section to learn more about them!"))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.d8cb77b7.js b/assets/js/runtime~main.d135f9fa.js similarity index 96% rename from assets/js/runtime~main.d8cb77b7.js rename to assets/js/runtime~main.d135f9fa.js index 55087a5..f7be29f 100644 --- a/assets/js/runtime~main.d8cb77b7.js +++ b/assets/js/runtime~main.d135f9fa.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,f,t,r,d={},c={};function o(e){var a=c[e];if(void 0!==a)return a.exports;var f=c[e]={exports:{}};return d[e].call(f.exports,f,f.exports,o),f.exports}o.m=d,e=[],o.O=(a,f,t,r)=>{if(!f){var d=1/0;for(i=0;i=r)&&Object.keys(o.O).every((e=>o.O[e](f[b])))?f.splice(b--,1):(c=!1,r0&&e[i-1][2]>r;i--)e[i]=e[i-1];e[i]=[f,t,r]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var r=Object.create(null);o.r(r);var d={};a=a||[null,f({}),f([]),f(f)];for(var c=2&t&&e;"object"==typeof c&&!~a.indexOf(c);c=f(c))Object.getOwnPropertyNames(c).forEach((a=>d[a]=()=>e[a]));return d.default=()=>e,o.d(r,d),r},o.d=(e,a)=>{for(var f in a)o.o(a,f)&&!o.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,f)=>(o.f[f](e,a),a)),[])),o.u=e=>"assets/js/"+({53:"935f2afb",319:"beef2e5e",533:"b2b675dd",971:"8f3f3eb4",1083:"ccf32e2c",1265:"0bc61828",1319:"d55b0726",1477:"b2f554cd",1506:"812929bd",1514:"c4d47cf3",2232:"79f1aba6",2361:"0767a432",2535:"814f3328",3089:"a6aa9e1f",3237:"1df93b7f",3608:"9e4087bc",3963:"f59250dd",3990:"aee02657",4080:"fad95dd6",4875:"9c74c623",5073:"688233d3",5460:"6b4fb629",5814:"75951831",5900:"afa146ea",5957:"9f2b9aaa",6103:"ccc49370",6451:"74194769",6807:"75f4f5e9",7395:"03f2f0ad",7548:"04079a71",7636:"3c0669b5",7853:"8e8df9b4",7867:"ddf62b55",7918:"17896441",7931:"3348f244",8197:"e954e22c",8246:"f4559a0a",8297:"0ad944d7",8595:"42992356",8794:"d0609b9f",9027:"930f736d",9514:"1be78505",9817:"14eb3368",9938:"32317fc3"}[e]||e)+"."+{53:"b334f709",319:"58f4ae47",533:"3f8b48ea",614:"56030aff",971:"8ce71b3f",1083:"5813766f",1265:"16b3f03b",1319:"8d3a2451",1477:"159e4b73",1506:"2a455537",1514:"4f45c0af",2232:"b778056e",2361:"ae2bac57",2535:"84cecb83",3089:"ca98e784",3237:"e5d664a0",3608:"16a464e6",3963:"57fe8177",3990:"8953db45",4080:"e759e496",4875:"5a14c151",4972:"0b304abc",5073:"9c456fd6",5460:"de8d5c63",5814:"27a44774",5900:"3d94a410",5957:"4c7b52e8",6103:"4bebef87",6451:"dda8911a",6807:"b832dff5",7395:"317d3c52",7548:"e1d236db",7636:"5819fd84",7853:"4649ab2f",7867:"8154ffc3",7918:"6e5a304e",7931:"a13fd2fb",8197:"a9958342",8218:"4970d5b8",8246:"54997f01",8297:"4c66e6fe",8595:"c45ec8ef",8794:"0a827d4f",9027:"286074af",9514:"ee0c957b",9739:"83252ac5",9817:"38e391a4",9938:"30e9ee55"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),t={},r="neohaskell-github-io:",o.l=(e,a,f,d)=>{if(t[e])t[e].push(a);else{var c,b;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var r=t[e];if(delete t[e],c.parentNode&&c.parentNode.removeChild(c),r&&r.forEach((e=>e(f))),a)return a(f)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),b&&document.head.appendChild(c)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"7918",42992356:"8595",74194769:"6451",75951831:"5814","935f2afb":"53",beef2e5e:"319",b2b675dd:"533","8f3f3eb4":"971",ccf32e2c:"1083","0bc61828":"1265",d55b0726:"1319",b2f554cd:"1477","812929bd":"1506",c4d47cf3:"1514","79f1aba6":"2232","0767a432":"2361","814f3328":"2535",a6aa9e1f:"3089","1df93b7f":"3237","9e4087bc":"3608",f59250dd:"3963",aee02657:"3990",fad95dd6:"4080","9c74c623":"4875","688233d3":"5073","6b4fb629":"5460",afa146ea:"5900","9f2b9aaa":"5957",ccc49370:"6103","75f4f5e9":"6807","03f2f0ad":"7395","04079a71":"7548","3c0669b5":"7636","8e8df9b4":"7853",ddf62b55:"7867","3348f244":"7931",e954e22c:"8197",f4559a0a:"8246","0ad944d7":"8297",d0609b9f:"8794","930f736d":"9027","1be78505":"9514","14eb3368":"9817","32317fc3":"9938"}[e]||e,o.p+o.u(e)},(()=>{var e={1303:0,532:0};o.f.j=(a,f)=>{var t=o.o(e,a)?e[a]:void 0;if(0!==t)if(t)f.push(t[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var r=new Promise(((f,r)=>t=e[a]=[f,r]));f.push(t[2]=r);var d=o.p+o.u(a),c=new Error;o.l(d,(f=>{if(o.o(e,a)&&(0!==(t=e[a])&&(e[a]=void 0),t)){var r=f&&("load"===f.type?"missing":f.type),d=f&&f.target&&f.target.src;c.message="Loading chunk "+a+" failed.\n("+r+": "+d+")",c.name="ChunkLoadError",c.type=r,c.request=d,t[1](c)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,f)=>{var t,r,d=f[0],c=f[1],b=f[2],n=0;if(d.some((a=>0!==e[a]))){for(t in c)o.o(c,t)&&(o.m[t]=c[t]);if(b)var i=b(o)}for(a&&a(f);n{"use strict";var e,a,f,t,r,d={},c={};function o(e){var a=c[e];if(void 0!==a)return a.exports;var f=c[e]={exports:{}};return d[e].call(f.exports,f,f.exports,o),f.exports}o.m=d,e=[],o.O=(a,f,t,r)=>{if(!f){var d=1/0;for(i=0;i=r)&&Object.keys(o.O).every((e=>o.O[e](f[b])))?f.splice(b--,1):(c=!1,r0&&e[i-1][2]>r;i--)e[i]=e[i-1];e[i]=[f,t,r]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var r=Object.create(null);o.r(r);var d={};a=a||[null,f({}),f([]),f(f)];for(var c=2&t&&e;"object"==typeof c&&!~a.indexOf(c);c=f(c))Object.getOwnPropertyNames(c).forEach((a=>d[a]=()=>e[a]));return d.default=()=>e,o.d(r,d),r},o.d=(e,a)=>{for(var f in a)o.o(a,f)&&!o.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,f)=>(o.f[f](e,a),a)),[])),o.u=e=>"assets/js/"+({53:"935f2afb",319:"beef2e5e",533:"b2b675dd",971:"8f3f3eb4",1083:"ccf32e2c",1265:"0bc61828",1319:"d55b0726",1477:"b2f554cd",1506:"812929bd",1514:"c4d47cf3",2232:"79f1aba6",2361:"0767a432",2535:"814f3328",3089:"a6aa9e1f",3237:"1df93b7f",3608:"9e4087bc",3963:"f59250dd",3990:"aee02657",4080:"fad95dd6",4875:"9c74c623",5073:"688233d3",5460:"6b4fb629",5814:"75951831",5900:"afa146ea",5957:"9f2b9aaa",6103:"ccc49370",6451:"74194769",6807:"75f4f5e9",7395:"03f2f0ad",7548:"04079a71",7636:"3c0669b5",7853:"8e8df9b4",7867:"ddf62b55",7918:"17896441",7931:"3348f244",8197:"e954e22c",8246:"f4559a0a",8297:"0ad944d7",8595:"42992356",8794:"d0609b9f",9027:"930f736d",9514:"1be78505",9817:"14eb3368",9938:"32317fc3"}[e]||e)+"."+{53:"2e7beb8a",319:"58f4ae47",533:"3f8b48ea",614:"56030aff",971:"8ce71b3f",1083:"5813766f",1265:"16b3f03b",1319:"8d3a2451",1477:"159e4b73",1506:"2a455537",1514:"4f45c0af",2232:"b778056e",2361:"ae2bac57",2535:"84cecb83",3089:"ca98e784",3237:"e5d664a0",3608:"16a464e6",3963:"57fe8177",3990:"8953db45",4080:"e759e496",4875:"5a14c151",4972:"0b304abc",5073:"9c456fd6",5460:"de8d5c63",5814:"27a44774",5900:"3d94a410",5957:"0ff5220e",6103:"4bebef87",6451:"dda8911a",6807:"03f47b32",7395:"317d3c52",7548:"e1d236db",7636:"5819fd84",7853:"4649ab2f",7867:"8154ffc3",7918:"6e5a304e",7931:"a13fd2fb",8197:"a9958342",8218:"4970d5b8",8246:"54997f01",8297:"4c66e6fe",8595:"c45ec8ef",8794:"5f50e1eb",9027:"286074af",9514:"ee0c957b",9739:"83252ac5",9817:"38e391a4",9938:"30e9ee55"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),t={},r="neohaskell-github-io:",o.l=(e,a,f,d)=>{if(t[e])t[e].push(a);else{var c,b;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var r=t[e];if(delete t[e],c.parentNode&&c.parentNode.removeChild(c),r&&r.forEach((e=>e(f))),a)return a(f)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),b&&document.head.appendChild(c)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"7918",42992356:"8595",74194769:"6451",75951831:"5814","935f2afb":"53",beef2e5e:"319",b2b675dd:"533","8f3f3eb4":"971",ccf32e2c:"1083","0bc61828":"1265",d55b0726:"1319",b2f554cd:"1477","812929bd":"1506",c4d47cf3:"1514","79f1aba6":"2232","0767a432":"2361","814f3328":"2535",a6aa9e1f:"3089","1df93b7f":"3237","9e4087bc":"3608",f59250dd:"3963",aee02657:"3990",fad95dd6:"4080","9c74c623":"4875","688233d3":"5073","6b4fb629":"5460",afa146ea:"5900","9f2b9aaa":"5957",ccc49370:"6103","75f4f5e9":"6807","03f2f0ad":"7395","04079a71":"7548","3c0669b5":"7636","8e8df9b4":"7853",ddf62b55:"7867","3348f244":"7931",e954e22c:"8197",f4559a0a:"8246","0ad944d7":"8297",d0609b9f:"8794","930f736d":"9027","1be78505":"9514","14eb3368":"9817","32317fc3":"9938"}[e]||e,o.p+o.u(e)},(()=>{var e={1303:0,532:0};o.f.j=(a,f)=>{var t=o.o(e,a)?e[a]:void 0;if(0!==t)if(t)f.push(t[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var r=new Promise(((f,r)=>t=e[a]=[f,r]));f.push(t[2]=r);var d=o.p+o.u(a),c=new Error;o.l(d,(f=>{if(o.o(e,a)&&(0!==(t=e[a])&&(e[a]=void 0),t)){var r=f&&("load"===f.type?"missing":f.type),d=f&&f.target&&f.target.src;c.message="Loading chunk "+a+" failed.\n("+r+": "+d+")",c.name="ChunkLoadError",c.type=r,c.request=d,t[1](c)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,f)=>{var t,r,d=f[0],c=f[1],b=f[2],n=0;if(d.some((a=>0!==e[a]))){for(t in c)o.o(c,t)&&(o.m[t]=c[t]);if(b)var i=b(o)}for(a&&a(f);n - +

NHEP 0 - Index of NeoHaskell Evolution Proposals

· One min read
Nick Tchayka
STATUS - DRAFT

This NHEP will contain the index of all NeoHaskell Enhancement Proposals, known as NHEPs. NHEP numbers are assigned by the NHEP editors, and once assigned are never changed. The Git history of the NHEP texts represent their historical record.

In the future, the NHEPs will be listed here by category. For now, use the sidebar on the left.

A good starting point is NHEP 1, which describes the NHEP process itself.

- + \ No newline at end of file diff --git a/blog/0001-purpose-and-guidelines/index.html b/blog/0001-purpose-and-guidelines/index.html index 3b06416..b083c1e 100644 --- a/blog/0001-purpose-and-guidelines/index.html +++ b/blog/0001-purpose-and-guidelines/index.html @@ -10,13 +10,13 @@ - +

NHEP 1 - Purpose and Guidelines

· 2 min read
Nick Tchayka
STATUS - IN PROGRESS

What is a NHEP?

NHEP stands for NeoHaskell Evolution Proposal. It is a document that describes a change or addition to NeoHaskell.

Statuses of a NHEP

A NHEP can have one of the following statuses:

STATUS - DRAFT

This status indicates that the NHEP is still being written and is not ready for review.

STATUS - IN PROGRESS

This status indicates that the NHEP has been accepted but is still being implemented.

STATUS - INTEGRATED

This status indicates that the NHEP has been implemented.

STATUS - ACCEPTED

This status is for informational NHEPs that have been accepted.

STATUS - REJECTED

This status indicates that the NHEP has been rejected.

How to contribute to the design process

Everyone is welcome to propose, discuss, and review ideas to improve NeoHaskell in the #proposals channel of the Discord server.

Note that the project is in a very early stage, and the contribution to the design process is not well defined.

As some general rules for now, take this into account before submitting a proposal:

  • No "What about if NeoHaskell does a 180-degree turn and instead does this completely unrelated thing?". These kinds of proposals are seen as completely out of the scope of the NeoHaskell project and will be instantly dismissed.
  • Use constructive criticism. Instead of "remove this, I don't like it", take a moment to think and give actual reasons like "I believe that this function in the standard library is not clear enough, someone could understand this in a wrong way"
  • Is the problem being addressed impactful enough to warrant a change to NeoHaskell?
  • How does this impact the Principle of Least Astonishment of the project?
  • How does this impact the Principle of Developer Happiness of the project?
  • How does this impact the Principle of Least Effort of the project? Both externally for the users and internally for the maintainers.
  • Does this proposal fit well with the feel and direction of NeoHaskell?
  • What other libraries/languages got you inspired to submit this proposal? How does this compare to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Please state explicitly whether you believe that the proposal should be accepted into NeoHaskell.

- + \ No newline at end of file diff --git a/blog/0002-project-target/index.html b/blog/0002-project-target/index.html index 5dec06e..f9cac81 100644 --- a/blog/0002-project-target/index.html +++ b/blog/0002-project-target/index.html @@ -10,13 +10,13 @@ - +

NHEP 2 - Target and User Persona

· 5 min read
Nick Tchayka
STATUS - ACCEPTED

Introduction

This document defines the target audience and user persona for the NeoHaskell project.

The definition of this target audience and user persona is intended to guide the design and development of the NeoHaskell project. It is not intended to be a strict definition of who can or cannot use the NeoHaskell project, only a guide to help the project team make decisions. Everyone is welcome to use NeoHaskell, regardless of whether they fit the target audience or user persona.

If a user doesn't fit the target audience that doesn't mean that they shouldn't use NeoHaskell, it just means that the design process and showcasing process won't be prioritizing them.

Target Audience

The target audience of NeoHaskell are developers who have a maker/hacker philosophy, who are comfortable with a statically typed mainstream language like TypeScript or Java, and who are interested in developing a project as fast as possible to test an idea or prototype a product, even if they aren't the most experienced software developers. They are not interested in spending a lot of time learning a new programming language in-depth, they are not interested in learning software architecture in-depth, and they are not interested in spending a lot of time learning a new framework.

They are interested in learning new things, as long as those are steps in the direction of their goal. They are willing to learn a new programming language, as long as they can start writing code in a few hours. They are willing to learn a new framework, as long as they can start using it in a few hours. They are willing to learn a new architecture, as long as they can start using it in a few hours.

These are not interested in having a perfect code quality from the beginning, so they won't be investing time in thoroughly testing their code, regardless of the testing methodology. And although they do not know it, they value working in a domain-driven way.

They will probably be interested in learning more about the programming language, the framework, and the architecture, but only after they have a working prototype of their project.

They don't have enough time to spend on their projects, probably because they have a full-time job, or they are students, or they have a family, or they have other hobbies. So they prefer a command line tool that does everything for them, instead of having to research the different tools for their technology stack, and then having to configure them to work together.

Who's not the target audience

Well-seasoned developers who have experience either in Haskell or in functional programming in general, and who are looking to apply concepts well-established in the functional world.

Users who are interested in type theory, category theory, programming language theory, and other advanced topics in computer science.

Again, if a user doesn't fit the target audience doesn't mean that they shouldn't use NeoHaskell, it just means that the design process and showcasing process won't be prioritizing them.

User Persona

  • Name: Jess
  • Role: Junior Software Developer

Actions, Motivations, and Pains

  • What do I do?
    • Work full-time as a software developer in a TypeScript software shop.
    • In the evenings and weekends I try to build up my portfolio by working on side projects.
    • Learning good practices, patterns, and technologies that act as leverage for my learning process.
  • Why do I do it?
    • I want to improve my skills as a software developer.
    • Having a good portfolio will help me get a better job.
    • One of my projects might become a startup.
  • What do I want?
    • I want to build a project as fast as possible.
    • I want to have negotiation power by mentioning skills that are relevant in the market, regardless of the technology stack.
    • I want to have fun while learning.
  • What's stopping me?
    • There are a lot of things to learn, and I don't know where to start.
    • Many times, existing tools have so many pitfalls that I end up spending more time fixing them than actually working on my project, which is frustrating.
    • I don't have enough experience to reason too much about all the side-effects that are happening under the hoods, that make my code behave in unexpected ways. This leads to a lot of frustration, and I end up thinking that I'm not good enough to be a software developer.

Values

  • What convinces me?
    • Having good documentation that explains the concepts that I need to know.
    • Having recipes and precise instructions on the steps I need to follow to achieve my goals.
    • Being able to understand the code that I'm looking at.
    • Success stories of other people of my skill level who have achieved similar goals as mine.
  • What or who informs me?
    • Dev.to
    • Friends and colleagues
    • Twitter

Context

  • Where am I?
    • At home
    • At a coffee shop
    • On public transportation
  • What's my day to day?
    • One hour commute to and from work
    • Attend daily standup meetings
    • Work on my assigned tasks
    • Have to do groceries and other chores after work
    • Spend time with my family
    • If there's time left, work on my side projects

Conclusion

Here we describe the target audience and user persona for NeoHaskell. All the design and development decisions will be made with this target audience and user persona in mind.

We will try to make the project as accessible as possible for these kinds of users. So they can enjoy even the last 15 minutes of the day, where they have time to work on their side projects.

- + \ No newline at end of file diff --git a/blog/0003-principles-of-design/index.html b/blog/0003-principles-of-design/index.html index 0cb7752..3889b74 100644 --- a/blog/0003-principles-of-design/index.html +++ b/blog/0003-principles-of-design/index.html @@ -10,7 +10,7 @@ - + @@ -20,7 +20,7 @@ the target audience and user persona are kept in mind at all times, as they are the most important stakeholders of the project.

Principle of Least Astonishment

The Principle of Least Astonishment, also known as the Principle of Least Surprise, is a guideline in user interface and software design that emphasizes creating systems that behave in a manner consistent with user expectations, thereby minimizing user surprise and confusion. The principle advises that system components should act in alignment with widely accepted conventions and their syntactical indications. It encourages designers to ensure that the behavior of a system or feature closely matches users' existing knowledge and expectations to reduce the learning curve and prevent user astonishment. Although the principle originated in the late 1960s, it remains relevant in different technological platforms and settings to ensure that functionalities such as keyboard shortcuts, widgets, or method names are intuitive and adhere to familiar patterns for users, thereby enhancing user experience and system usability.

Examples

Those might not look like much, but they are details that end up stacking and streamlining the overall experience. The aim is to reduce the cognitive load on the user, and they are already learning a new language and ecosystem, so we should remove as much stuff to learn as possible. For example, promoting:

  • JSON or YAML as configuration formats instead of TOML or Dhall.
  • Git as the version control software instead of Darcs or Pijul.
  • GitHub as the code hosting platform instead of GitLab or BitBucket.
  • Visual Studio Code as the IDE instead of Emacs or Vim.

Principle of Developer Happiness

The Principle of Developer Happiness prioritizes establishing an environment that aligns with software developers' professional and personal expectations, thereby enhancing satisfaction and retention. It underscores creating engaging experiences and a supportive culture where developers are fairly compensated, and find alignment with the group's mission. Additionally, it advocates for the use of efficient tools and technologies to streamline the development process and save time. Finally, it emphasizes continuous visibility into and assessment of developer efficiency through surveys and feedback, ensuring a continually optimized working environment that resonates with developer needs and expectations. This principle aims to meld operational strategies with developer satisfaction, ensuring an atmosphere that intuitively supports developers.

Examples

This principle emphasizes creating an environment that aligns with the expectations of both developers and users of NeoHaskell. It centers around fostering satisfaction, engagement, and a supportive community. Here are some examples:

  • Clear Documentation: Provide user-friendly and comprehensive documentation with code examples.
  • Community Engagement: Encourage active participation and collaboration within the open-source community.
  • Transparent Decision-Making: Maintain transparency in language development decisions.
  • Inclusive Onboarding: Offer resources for developers of all skill levels.
  • Recognition: Acknowledge and appreciate contributions and projects from the community.
  • Feedback Channels: Create accessible channels for users and contributors to provide input and report issues.

Principle of Least Effort

The Principle of Least Effort underscores the axiom that entities, within a given context, will opt for the solution that requires the minimal amount of work or complexity to achieve a specific goal. Applied within the realm of software design and development, it emphasizes creating systems and interfaces that are straightforward, easy to comprehend, and simple to interact with, thereby reducing the cognitive and operational load on both the user and developer. On the user's side, interfaces should be intuitive, providing the easiest pathway to accomplish tasks with minimum steps and complexity. For developers, this principle encourages the creation of code and architectures that are clean, succinct, and straightforward to understand and modify. Ultimately, adhering to the Principle of Least Effort enables the creation of more user-friendly applications and sustainably maintainable codebases, promoting efficient and effective interactions for all involved parties.

Examples

  • Simple Syntax: Design the language with a straightforward and intuitive syntax to reduce the cognitive load on developers using the language.
  • Clear and Concise Documentation: Provide comprehensive yet concise documentation to help users understand and utilize the language effectively without unnecessary complexity.
  • Standard Libraries: Include a well-organized standard library that simplifies common programming tasks, reducing the effort required to implement them from scratch.
  • Error Handling: Implement user-friendly error messages and handling mechanisms that assist developers in identifying and resolving issues efficiently.
  • Community Support: Foster a supportive community where developers can seek help, share knowledge, and collaborate, reducing the effort needed to overcome challenges.
  • Version Management: Streamline version management and updates to make it easy for users to adopt new language features and improvements.

Conclusion

In conclusion, the design principles outlined for NeoHaskell serve as a fundamental compass for the project's development and implementation processes. The Principle of Least Astonishment underscores the importance of aligning system behavior with user expectations, enhancing user experience and usability. The Principle of Developer Happiness focuses on creating an environment that satisfies both developers and users, emphasizing engagement, transparency, and continuous improvement. Lastly, the Principle of Least Effort promotes simplicity and efficiency, reducing the cognitive and operational load on users and developers alike.

These principles collectively ensure that NeoHaskell remains user-centric, developer-friendly, and efficient throughout its development journey. By adhering to these guiding principles, NeoHaskell aims to not only meet but exceed the expectations of its target audience and user persona, ultimately leading to a successful and user-satisfying language.

- + \ No newline at end of file diff --git a/blog/0004-semantic-versioning/index.html b/blog/0004-semantic-versioning/index.html index fc5db2d..79785db 100644 --- a/blog/0004-semantic-versioning/index.html +++ b/blog/0004-semantic-versioning/index.html @@ -10,13 +10,13 @@ - +

NHEP 4 - Semantic Versioning

· 3 min read
Nick Tchayka
STATUS - ACCEPTED

Introduction

In the context of the NeoHaskell ecosystem, we employ the Semantic Versioning (SemVer) schema. SemVer consists of a set of rules and guidelines for assigning and incrementing version numbers within our software development process. By embracing SemVer, NeoHaskell addresses the intricacies of managing dependencies, especially as the ecosystem complexity evolves. The primary objective of utilizing Semantic Versioning within the NeoHaskell ecosystem is to ensure that our version numbers are both transparent and informative, effectively communicating the nature of changes made to our software.

Impact on Principle of Least Astonishment

Semantic Versioning has a positive impact on the Principle of Least Astonishment. First of all, it is a very popular schema, used by many open-source projects. Second, adhering to clear and consistent rules for version numbering reduces confusion and surprises for developers and users of software packages. When version numbers follow a predictable pattern (Major.Minor.Patch), it becomes easier for stakeholders to understand the significance of each release. This predictability enhances the user experience and makes it easier to manage software dependencies.

Impact on Principle of Developer Happiness

Semantic Versioning contributes to the Principle of Developer Happiness. It provides developers with a systematic approach to versioning, which simplifies the process of releasing and upgrading software packages. Developers can confidently make changes to their codebase while following SemVer guidelines, knowing that version numbers convey the impact of those changes. This reduces the stress associated with managing dependencies and allows developers to focus on building and improving their software.

Impact on Principle of Least Effort

Semantic Versioning aligns with the Principle of Least Effort by streamlining the management of software dependencies. When developers adhere to SemVer, they can make backward-compatible changes without having to release new major versions. This reduces the effort required to maintain and update software packages. Additionally, clear documentation of public APIs and the use of version numbers to indicate compatibility further simplify the integration of dependencies, minimizing the effort needed to ensure smooth interactions between software components.

Usage of SemVer in early development phases

During the early development of the NeoHaskell project, the major version will be kept at 0, so that the community can expect breaking changes to occur frequently. Once the project reaches a stable state, the major version will be incremented to 1, indicating that breaking changes will be made less frequently.

Standardizing the use of SemVer

The Semantic Versioning schema is advised as a standard for all NeoHaskell projects, as it will help all of the ecosystem to be in sync.

The neo CLI tool will easily help bumping versions, as it will inspect the exported functions and types and will suggest the next version based on the changes made.

Considerations

Note that even though the API of a package is not changed, the implementation of a function can be changed, which can lead to different results. This is considered a breaking change and should be reflected in the version number.

Take a look at this GitHub thread for examples and more information.

- + \ No newline at end of file diff --git a/blog/0005-better-default-string-type/index.html b/blog/0005-better-default-string-type/index.html index 08fb2f6..d385d8e 100644 --- a/blog/0005-better-default-string-type/index.html +++ b/blog/0005-better-default-string-type/index.html @@ -10,13 +10,13 @@ - +

NHEP 5 - Better Default String Type

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

By default, Haskell's String type is only an alias for [Char] and as such is inefficient for non-trivial uses (e.g. due to slow appends). The text package provides a more sensible Text type and has become essentially the standard within the Haskell ecosystem.

NeoHaskell should use Text as its default string type under the more familiar name String and support literals via the standard "double quoted" syntax.

Impact on Principle of Least Astonishment

The fact that a simple string literal is inefficient and usually undesirable violates the Principle of Least Astonishment. Most new programmers expect String and "string literals" to work as expected without any additional effort. This change will align NeoHaskell with those expectations.

Impact on Principle of Developer Happiness

As noted below, this change will require less boilerplate of developers and allow them to get to actual code faster. Eliminating the use of OverloadedStrings will also cut down on unexpected type errors.

Impact on Principle of Least Effort

Currently, nearly every Haskell project requires a dependency on text and files are littered with import Data.Texts and {-# LANGUAGE OverloadedStrings #-}s. This change will eliminate all of that overhead and allow the use of a sensible string type with better ergonomics.

Implementation

In early stages, this can be achieved via prelude and pragmas by newtypeing Text to String and use of OverloadedStrings with:

default IsString String -- where String is the new type over Text

Considerations

Since Text is the standard for most of the Haskell ecosystem, this change is unlikely to have many negative ramifications, other than possible confusion over the String type in NeoHaskell actually being Text and not String, but this is only of concern for developers stepping outside of the NeoHaskell ecosystem.

Another consideration is the existence of the other common data type ByteString. Text is more often the sensible default for all except extremely performance-critical applications, so this should not be a problem.

- + \ No newline at end of file diff --git a/blog/0006-better-default-list-type/index.html b/blog/0006-better-default-list-type/index.html index 0675744..c32cf91 100644 --- a/blog/0006-better-default-list-type/index.html +++ b/blog/0006-better-default-list-type/index.html @@ -10,13 +10,13 @@ - +

NHEP 6 - Better Default List Type

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

By default, Haskell's List type is a simple linked list and as such is inefficient for many common operations (getting by index, appending, iterating from the end, etc.). A number of other more efficient list-like data structures are available, most notably the array-like Vector and the list-like Seq.

NeoHaskell should choose one of these two data structures as its default list type and support literals via the standard [1, 2, 3] syntax.

Impact on Principle of Least Astonishment

The fact that a simple list literal is inefficient to append or get by index and usually undesirable violates the Principle of Least Astonishment. Most new programmers expect sytactic sugar like [1, 2, 3]" to produce a commonly desirable data type without any additional effort. This change will align NeoHaskell with those expectations.

Impact on Principle of Developer Happiness

As noted below, this change will require less boilerplate of developers and allow them to get to actual code faster, as well as gently guiding developers to choose a data type without them having to decide which to use.

Impact on Principle of Least Effort

Currently, nearly every Haskell project requires a dependency on containers and/or vector and files are littered with imports and {-# LANGUAGE OverloadedList #-}s. This change will eliminate all of that overhead and allow the use of a sensible list type with better ergonomics.

Implementation

In early stages, this can be achieved via prelude and pragmas by the use of OverloadedLists with:

default IsList Vector -- or Seq

Considerations

Haskell's default lists are more efficient for LIFO queues than either of these data types. There could, however, be a Queue data type or the like to push developers in that when it's desirable.

List comprehensions (if they will be in NeoHaskell) would need to support this data type as well.

Vector is not pattern-matchable on. Seq does not have this issue, which might be a point in favor of it.

- + \ No newline at end of file diff --git a/blog/0007-more-concise-type-signature-syntax/index.html b/blog/0007-more-concise-type-signature-syntax/index.html index 4ec7734..f628992 100644 --- a/blog/0007-more-concise-type-signature-syntax/index.html +++ b/blog/0007-more-concise-type-signature-syntax/index.html @@ -10,13 +10,13 @@ - +

NHEP 7 - More Concise Type Signature Syntax

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

Haskell uses :: for type signatures (and the shorter : for cons). Type signatures are generally far, far more numerous than cons operations/pattern matches, and so most Haskell-descended languages have chosen to use : for type signatures instead, for the sake of cleaner-looking code and fewer developer keypresses. : for cons does highlight the importance of lists in the original conception of Haskell, but with the default list type possibly changing (per NHEP 6), it will likely be de-emphasized in NeoHaskell.

NeoHaskell should use a single colon : to denote type signatures instead.

Impact on Principle of Least Astonishment

Most languages descended from Haskell have made this change, including Elm, Idris, and Agda. Additionally, many unrelated languages use the same syntax for types, including F#, Dhall, and OCaml, or similar syntax featuring a single colon, e.g. TypeScript, Elixir, Rust, and Python (for type hints).

Almost no other language except Purescript uses :: while all of the aforementioned use : in some capacity, so this change will make NeoHaskell's syntax more intuitive to non-Haskell developers.

Impact on Principle of Developer Happiness

Since there are far, far more type signatures than cons in most Haskell code, this change renders syntax visually cleaner, more familiar, and reduces the number of required keypresses.

Impact on Principle of Least Effort

As mentioned above, this change reduces the number of required keypresses to write a type signature.

Considerations

This will require a change to the cons operator of lists. :: is of course available, though that could perhaps be used for a new default list type instead per NHEP 6. Regardless, this decision must be made as well.

This change will render NeoHaskell slightly less familiar to Haskell developers. However, as noted above, most other languages descended from Haskell have made the same change, and it is unlikely to cause significant friction.

- + \ No newline at end of file diff --git a/blog/archive/index.html b/blog/archive/index.html index 96f3875..2e5b52e 100644 --- a/blog/archive/index.html +++ b/blog/archive/index.html @@ -10,13 +10,13 @@ - + - + \ No newline at end of file diff --git a/blog/index.html b/blog/index.html index 36320ed..961a9ce 100644 --- a/blog/index.html +++ b/blog/index.html @@ -10,7 +10,7 @@ - + @@ -20,7 +20,7 @@ the target audience and user persona are kept in mind at all times, as they are the most important stakeholders of the project.

Principle of Least Astonishment

The Principle of Least Astonishment, also known as the Principle of Least Surprise, is a guideline in user interface and software design that emphasizes creating systems that behave in a manner consistent with user expectations, thereby minimizing user surprise and confusion. The principle advises that system components should act in alignment with widely accepted conventions and their syntactical indications. It encourages designers to ensure that the behavior of a system or feature closely matches users' existing knowledge and expectations to reduce the learning curve and prevent user astonishment. Although the principle originated in the late 1960s, it remains relevant in different technological platforms and settings to ensure that functionalities such as keyboard shortcuts, widgets, or method names are intuitive and adhere to familiar patterns for users, thereby enhancing user experience and system usability.

Examples

Those might not look like much, but they are details that end up stacking and streamlining the overall experience. The aim is to reduce the cognitive load on the user, and they are already learning a new language and ecosystem, so we should remove as much stuff to learn as possible. For example, promoting:

  • JSON or YAML as configuration formats instead of TOML or Dhall.
  • Git as the version control software instead of Darcs or Pijul.
  • GitHub as the code hosting platform instead of GitLab or BitBucket.
  • Visual Studio Code as the IDE instead of Emacs or Vim.

Principle of Developer Happiness

The Principle of Developer Happiness prioritizes establishing an environment that aligns with software developers' professional and personal expectations, thereby enhancing satisfaction and retention. It underscores creating engaging experiences and a supportive culture where developers are fairly compensated, and find alignment with the group's mission. Additionally, it advocates for the use of efficient tools and technologies to streamline the development process and save time. Finally, it emphasizes continuous visibility into and assessment of developer efficiency through surveys and feedback, ensuring a continually optimized working environment that resonates with developer needs and expectations. This principle aims to meld operational strategies with developer satisfaction, ensuring an atmosphere that intuitively supports developers.

Examples

This principle emphasizes creating an environment that aligns with the expectations of both developers and users of NeoHaskell. It centers around fostering satisfaction, engagement, and a supportive community. Here are some examples:

  • Clear Documentation: Provide user-friendly and comprehensive documentation with code examples.
  • Community Engagement: Encourage active participation and collaboration within the open-source community.
  • Transparent Decision-Making: Maintain transparency in language development decisions.
  • Inclusive Onboarding: Offer resources for developers of all skill levels.
  • Recognition: Acknowledge and appreciate contributions and projects from the community.
  • Feedback Channels: Create accessible channels for users and contributors to provide input and report issues.

Principle of Least Effort

The Principle of Least Effort underscores the axiom that entities, within a given context, will opt for the solution that requires the minimal amount of work or complexity to achieve a specific goal. Applied within the realm of software design and development, it emphasizes creating systems and interfaces that are straightforward, easy to comprehend, and simple to interact with, thereby reducing the cognitive and operational load on both the user and developer. On the user's side, interfaces should be intuitive, providing the easiest pathway to accomplish tasks with minimum steps and complexity. For developers, this principle encourages the creation of code and architectures that are clean, succinct, and straightforward to understand and modify. Ultimately, adhering to the Principle of Least Effort enables the creation of more user-friendly applications and sustainably maintainable codebases, promoting efficient and effective interactions for all involved parties.

Examples

  • Simple Syntax: Design the language with a straightforward and intuitive syntax to reduce the cognitive load on developers using the language.
  • Clear and Concise Documentation: Provide comprehensive yet concise documentation to help users understand and utilize the language effectively without unnecessary complexity.
  • Standard Libraries: Include a well-organized standard library that simplifies common programming tasks, reducing the effort required to implement them from scratch.
  • Error Handling: Implement user-friendly error messages and handling mechanisms that assist developers in identifying and resolving issues efficiently.
  • Community Support: Foster a supportive community where developers can seek help, share knowledge, and collaborate, reducing the effort needed to overcome challenges.
  • Version Management: Streamline version management and updates to make it easy for users to adopt new language features and improvements.

Conclusion

In conclusion, the design principles outlined for NeoHaskell serve as a fundamental compass for the project's development and implementation processes. The Principle of Least Astonishment underscores the importance of aligning system behavior with user expectations, enhancing user experience and usability. The Principle of Developer Happiness focuses on creating an environment that satisfies both developers and users, emphasizing engagement, transparency, and continuous improvement. Lastly, the Principle of Least Effort promotes simplicity and efficiency, reducing the cognitive and operational load on users and developers alike.

These principles collectively ensure that NeoHaskell remains user-centric, developer-friendly, and efficient throughout its development journey. By adhering to these guiding principles, NeoHaskell aims to not only meet but exceed the expectations of its target audience and user persona, ultimately leading to a successful and user-satisfying language.

· 3 min read
Nick Tchayka
STATUS - ACCEPTED

Introduction

In the context of the NeoHaskell ecosystem, we employ the Semantic Versioning (SemVer) schema. SemVer consists of a set of rules and guidelines for assigning and incrementing version numbers within our software development process. By embracing SemVer, NeoHaskell addresses the intricacies of managing dependencies, especially as the ecosystem complexity evolves. The primary objective of utilizing Semantic Versioning within the NeoHaskell ecosystem is to ensure that our version numbers are both transparent and informative, effectively communicating the nature of changes made to our software.

Impact on Principle of Least Astonishment

Semantic Versioning has a positive impact on the Principle of Least Astonishment. First of all, it is a very popular schema, used by many open-source projects. Second, adhering to clear and consistent rules for version numbering reduces confusion and surprises for developers and users of software packages. When version numbers follow a predictable pattern (Major.Minor.Patch), it becomes easier for stakeholders to understand the significance of each release. This predictability enhances the user experience and makes it easier to manage software dependencies.

Impact on Principle of Developer Happiness

Semantic Versioning contributes to the Principle of Developer Happiness. It provides developers with a systematic approach to versioning, which simplifies the process of releasing and upgrading software packages. Developers can confidently make changes to their codebase while following SemVer guidelines, knowing that version numbers convey the impact of those changes. This reduces the stress associated with managing dependencies and allows developers to focus on building and improving their software.

Impact on Principle of Least Effort

Semantic Versioning aligns with the Principle of Least Effort by streamlining the management of software dependencies. When developers adhere to SemVer, they can make backward-compatible changes without having to release new major versions. This reduces the effort required to maintain and update software packages. Additionally, clear documentation of public APIs and the use of version numbers to indicate compatibility further simplify the integration of dependencies, minimizing the effort needed to ensure smooth interactions between software components.

Usage of SemVer in early development phases

During the early development of the NeoHaskell project, the major version will be kept at 0, so that the community can expect breaking changes to occur frequently. Once the project reaches a stable state, the major version will be incremented to 1, indicating that breaking changes will be made less frequently.

Standardizing the use of SemVer

The Semantic Versioning schema is advised as a standard for all NeoHaskell projects, as it will help all of the ecosystem to be in sync.

The neo CLI tool will easily help bumping versions, as it will inspect the exported functions and types and will suggest the next version based on the changes made.

Considerations

Note that even though the API of a package is not changed, the implementation of a function can be changed, which can lead to different results. This is considered a breaking change and should be reflected in the version number.

Take a look at this GitHub thread for examples and more information.

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

By default, Haskell's String type is only an alias for [Char] and as such is inefficient for non-trivial uses (e.g. due to slow appends). The text package provides a more sensible Text type and has become essentially the standard within the Haskell ecosystem.

NeoHaskell should use Text as its default string type under the more familiar name String and support literals via the standard "double quoted" syntax.

Impact on Principle of Least Astonishment

The fact that a simple string literal is inefficient and usually undesirable violates the Principle of Least Astonishment. Most new programmers expect String and "string literals" to work as expected without any additional effort. This change will align NeoHaskell with those expectations.

Impact on Principle of Developer Happiness

As noted below, this change will require less boilerplate of developers and allow them to get to actual code faster. Eliminating the use of OverloadedStrings will also cut down on unexpected type errors.

Impact on Principle of Least Effort

Currently, nearly every Haskell project requires a dependency on text and files are littered with import Data.Texts and {-# LANGUAGE OverloadedStrings #-}s. This change will eliminate all of that overhead and allow the use of a sensible string type with better ergonomics.

Implementation

In early stages, this can be achieved via prelude and pragmas by newtypeing Text to String and use of OverloadedStrings with:

default IsString String -- where String is the new type over Text

Considerations

Since Text is the standard for most of the Haskell ecosystem, this change is unlikely to have many negative ramifications, other than possible confusion over the String type in NeoHaskell actually being Text and not String, but this is only of concern for developers stepping outside of the NeoHaskell ecosystem.

Another consideration is the existence of the other common data type ByteString. Text is more often the sensible default for all except extremely performance-critical applications, so this should not be a problem.

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

By default, Haskell's List type is a simple linked list and as such is inefficient for many common operations (getting by index, appending, iterating from the end, etc.). A number of other more efficient list-like data structures are available, most notably the array-like Vector and the list-like Seq.

NeoHaskell should choose one of these two data structures as its default list type and support literals via the standard [1, 2, 3] syntax.

Impact on Principle of Least Astonishment

The fact that a simple list literal is inefficient to append or get by index and usually undesirable violates the Principle of Least Astonishment. Most new programmers expect sytactic sugar like [1, 2, 3]" to produce a commonly desirable data type without any additional effort. This change will align NeoHaskell with those expectations.

Impact on Principle of Developer Happiness

As noted below, this change will require less boilerplate of developers and allow them to get to actual code faster, as well as gently guiding developers to choose a data type without them having to decide which to use.

Impact on Principle of Least Effort

Currently, nearly every Haskell project requires a dependency on containers and/or vector and files are littered with imports and {-# LANGUAGE OverloadedList #-}s. This change will eliminate all of that overhead and allow the use of a sensible list type with better ergonomics.

Implementation

In early stages, this can be achieved via prelude and pragmas by the use of OverloadedLists with:

default IsList Vector -- or Seq

Considerations

Haskell's default lists are more efficient for LIFO queues than either of these data types. There could, however, be a Queue data type or the like to push developers in that when it's desirable.

List comprehensions (if they will be in NeoHaskell) would need to support this data type as well.

Vector is not pattern-matchable on. Seq does not have this issue, which might be a point in favor of it.

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

Haskell uses :: for type signatures (and the shorter : for cons). Type signatures are generally far, far more numerous than cons operations/pattern matches, and so most Haskell-descended languages have chosen to use : for type signatures instead, for the sake of cleaner-looking code and fewer developer keypresses. : for cons does highlight the importance of lists in the original conception of Haskell, but with the default list type possibly changing (per NHEP 6), it will likely be de-emphasized in NeoHaskell.

NeoHaskell should use a single colon : to denote type signatures instead.

Impact on Principle of Least Astonishment

Most languages descended from Haskell have made this change, including Elm, Idris, and Agda. Additionally, many unrelated languages use the same syntax for types, including F#, Dhall, and OCaml, or similar syntax featuring a single colon, e.g. TypeScript, Elixir, Rust, and Python (for type hints).

Almost no other language except Purescript uses :: while all of the aforementioned use : in some capacity, so this change will make NeoHaskell's syntax more intuitive to non-Haskell developers.

Impact on Principle of Developer Happiness

Since there are far, far more type signatures than cons in most Haskell code, this change renders syntax visually cleaner, more familiar, and reduces the number of required keypresses.

Impact on Principle of Least Effort

As mentioned above, this change reduces the number of required keypresses to write a type signature.

Considerations

This will require a change to the cons operator of lists. :: is of course available, though that could perhaps be used for a new default list type instead per NHEP 6. Regardless, this decision must be made as well.

This change will render NeoHaskell slightly less familiar to Haskell developers. However, as noted above, most other languages descended from Haskell have made the same change, and it is unlikely to cause significant friction.

- + \ No newline at end of file diff --git a/docs/category/extra-topics/index.html b/docs/category/extra-topics/index.html index 2df0877..45e581d 100644 --- a/docs/category/extra-topics/index.html +++ b/docs/category/extra-topics/index.html @@ -10,13 +10,13 @@ - + - + + \ No newline at end of file diff --git a/docs/docs-intro/index.html b/docs/docs-intro/index.html index 61d6f5d..d570ba5 100644 --- a/docs/docs-intro/index.html +++ b/docs/docs-intro/index.html @@ -10,12 +10,12 @@ - +
-

Note on the Docs

NeoHaskell is an ongoing effort that is yet in an early development stage. Throughout the documentation you might find badges like the following:

Try hovering your mouse over it! (and if you click on it, it will take you to the GitHub issue that's tracking the implementation).

This badge means that this feature is documented but it is not implemented. This is on purpose.

We believe that the documentation is the central part of any good +

Note on the Docs

NeoHaskell is an ongoing effort that is yet in an early development stage. Throughout the documentation you might find badges like the following:

Try hovering your mouse over it! (and if you click on it, it will take you to the GitHub issue that's tracking the implementation).

This badge means that this feature is documented but it is not implemented. This is on purpose.

We believe that the documentation is the central part of any good development experience, therefore we first write the documentation, and only then, we implement the required things to make the feature defined in the documentation possible.

One of the primary statements of NeoHaskell is:

If it takes more than 15 minutes to figure out, it is a bug

This doesn't apply only to the command line tool, libraries, and so on, but also to the documentation itself.

Requesting Clarification

If you find yourself reading some part in the documentation and you're having trouble understanding it, click on the @@ -23,7 +23,7 @@ clarified.

This is Important

This process helps everyone to maintain a great documentation that everyone can understand, and these are actually the most important contributions, as when you have experience and knowledge about any topic it is very easy to miss on the actual details that newcomers can find daunting.

documentation beats conversation

Thanks for reading this part! Now onwards to learn NeoHaskell!

- + \ No newline at end of file diff --git a/docs/essentials/constants/index.html b/docs/essentials/constants/index.html index 766f36a..4b37111 100644 --- a/docs/essentials/constants/index.html +++ b/docs/essentials/constants/index.html @@ -10,12 +10,12 @@ - +
-

Constants

caution

The documentation that you're reading is a design document where most of +

Constants

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

NeoHaskell promotes the usage of variables that do not change over time. These kinds of variables are named constants.

In most of programming languages, what you expect is to use regular variables that can change, and then only if you think that some variable should not change, you create a constant. In NeoHaskell, the approach is the @@ -24,7 +24,7 @@ value on the right side of the equals sign, but instead, it is just storing the expression as it is.

When you try to use it (like when you write myConstant in the REPL), the compiler will calculate and store the value of the constant at that moment, as it is the first time that you are using it. This is called lazy evaluation, and we will go more in depth in the next section.

- + \ No newline at end of file diff --git a/docs/essentials/functions/index.html b/docs/essentials/functions/index.html index db4672c..5deb382 100644 --- a/docs/essentials/functions/index.html +++ b/docs/essentials/functions/index.html @@ -10,12 +10,12 @@ - +
-

Functions

caution

The documentation that you're reading is a design document where most of +

Functions

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

NeoHaskell promotes the style of programming that's called functional programming. There are so many definitions of functional programming, but it boils down to one thing: programming with functions.

Functions can be divided in two types: Calculations and Actions.

A calculation is a function that will always return the same result for the same input. In NeoHaskell, they cannot crash/throw an error. Examples of calculations are:

  • Performing mathematical operations (adding, subtracting, etc)
  • Returning the length of a list
  • Converting one object to another
  • Doing operations with strings

An action is a function that usually relies on some external thing to perform its job. These can throw errors. Examples of actions are:

  • Reading a file from the disk
  • Sending an email
  • Modifying some data in the state of the application
  • Printing something to the screen

This distinction is crucial, because when looking for a crash in your application, you can be sure that the problem is in an action.

In NeoHaskell, by default all functions are calculations. The compiler won't let you perform an action without explicitly @@ -25,8 +25,8 @@ returned, so there's no need to write return like in other languages.

Blocks are optional, and if we wanted to write the same function without it, removing all the constants, and writing the calculation inline, we could do it like this:

estimateShipping weight distance costPerKm =
distance * costPerKm + weight * 2

This way of writing code helps writing data processing code and validation rules in a very readable way. But be careful, -because one-liners can easily become hard to read, so it is advised to use blocks for more complex functions.

A Note on Cognitive Overhead

In the function we've defined above, we're naming our arguments weight, and distance. Is the weight in kilograms or pounds? Is the distance in kilometers, miles, or alligators? We don't know, and we might have to look at the implementation of the function to know. And still, we'd have no clue, because there aren't even comments in the function.

This is extra work that our brains must have to do, and when you're trying to navigate a codebase with hundreds (or thousands) of functions, it can become a nightmare. These kinds of issues might seem anecdotic, but actually the NASA lost millions due to a similar situation.

NeoHaskell takes an approach that helps you define explicit contracts for all of the functions. We do so by modeling our domain (the problem we're trying to solve) with types.

Let's hop into the next section to learn more about them!

- +because one-liners can easily become hard to read, so it is advised to use blocks for more complex functions.

A Note on Cognitive Overhead

In the function we've defined above, we're naming our arguments weight, and distance. Is the weight in kilograms or pounds? Is the distance in kilometers, miles, or alligators? We don't know, and we might have to look at the implementation of the function to know. And still, we'd have no clue, because there aren't even comments in the function.

This is extra work that our brains must have to do, and when you're trying to navigate a codebase with hundreds (or thousands) of functions, it can become a nightmare. These kinds of issues might seem anecdotic, but actually the NASA lost millions due to a similar situation.

NeoHaskell takes an approach that helps you define explicit contracts for all of the functions. We do so by modeling our domain (the problem we're trying to solve) with types.

Let's hop into the next section to learn more about them!

+ \ No newline at end of file diff --git a/docs/essentials/laziness/index.html b/docs/essentials/laziness/index.html index 2be4991..82c2b4b 100644 --- a/docs/essentials/laziness/index.html +++ b/docs/essentials/laziness/index.html @@ -10,12 +10,12 @@ - +
-

Laziness

caution

The documentation that you're reading is a design document where most of +

Laziness

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

NeoHaskell is a lazy language. Not only because it gives you a lot of tools so you can focus on what matters in your app, but also because it is lazily evaluated.

Lazy evaluation is especially beneficial because it can improve application load time, and it can also improve performance in some cases.

What is lazy evaluation?

Normally, in programming languages like JavaScript, when we create a variable and assign it a value, the value is calculated @@ -33,7 +33,7 @@ optimizations to make your code more performant.

For example, imagine that you have a single-page application that defines two completely different pages for registering a user: In one, the registered user is a person, in another the registered user is a company.

A naive implementation in JavaScript would be to have a single registerUser function that receives a userType parameter, and depending on the value of that parameter, it would perform a different action:

const registerPersonPage = // ...
const registerCompanyPage = // ...

if (userType === "person") {
return registerPersonPage;
} else {
return registerCompanyPage;
}

You see the issue here, both pages are being loaded when the code is loaded, even though only one of them will be used.

In contrast, in NeoHaskell, the pages wouldn't be loaded until they are needed, because their values would be lazily evaluated.

- + \ No newline at end of file diff --git a/docs/essentials/math/index.html b/docs/essentials/math/index.html index 83b6b94..4ef0221 100644 --- a/docs/essentials/math/index.html +++ b/docs/essentials/math/index.html @@ -10,17 +10,17 @@ - +
-

Doing Math

caution

The documentation that you're reading is a design document where most of +

Doing Math

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

As the Professor Walter White said once, "Jesse, let's cook some math", or something like that, I don't know.

Anyways, let's get to the point.

Basic Math

Here's a table of the typical math operators in NeoHaskell and what they do:

OperatorDescription
+Adds two numbers
-Subtracts two numbers
*Multiplies two numbers
/Divides two numbers
**Raises a number to a power
%Gets the remainder of a division
>>Shift right binary operation
<<Shift left binary operation

Trying it in the REPL

Addition Example

Lets say that I want to add 56 and 72 and find its result, I can do it as shown:

neo> 56+72
128

Subtraction Example

In this example I am subtracting 64 from 112

neo> 112-64
48

Division Example

Lets say I want to divide 117 by 12 and find the quotient, I can do it in NeoHaskell like this:

neo> 117/12
9

Power Example

Lets say I want to find what we will get by cubing five (five raised to the power of three), I can do it in NeoHaskell as shown:

neo> 5**3
125

Remainder Example

I want to know what we will get as remainder when we divide -21 by 4, I can do it as shown:

neo> 21%4
-1

Right Shift Example

This operator shifts the bits (the 1's and 0's of the computer representation) of a number to the right by a specified number of bits. For example, if we shift the bits of 5 to the right by 2 bits, we will get 1. This is because 5 in binary is 101, and when we shift it to the right by 2 bits, we get 1, which is 001 in binary.

Graphical explanation of the above
Source: https://www.geeksforgeeks.org/left-shift-right-shift-operators-c-cpp/
neo> 5>>2
1

Left Shift Example

This operator shifts the bits (the 1's and 0's of the computer representation) of a number to the left by a specified number of bits. For example, if we shift the bits of 5 to the left by 2 bits, we will get 20. This is because 5 in binary is 101, and when we shift it to the left by 2 bits, we get 20, which is 10100 in binary.

Graphical explanation of the above
Source: https://www.geeksforgeeks.org/left-shift-right-shift-operators-c-cpp/
neo> 5<<2
20

Spacing doesn't matter

NeoHaskell doesn't care about spacing, you can write the above examples as shown below and it will still work:

neo> 56 + 72
128

neo> 112 - 64
48

neo> 117 / 12
9

neo> 5 **3
125

More Advanced Math

More advanced math functions are available in the Math module. It is available by default, so you don't need to import it.

Modules? Import? What??

Don't worry, we'll get to that later. For now, just know that you can use do some math operations like this:

neo> Math.sin 90
0.8939966636005579

neo> Math.sqrt 64
8

neo> Math.log 10
2.302585092994046

You can experiment and explore the advanced math functions available by typing Math. and then pressing TAB to see the list of functions.

- + \ No newline at end of file diff --git a/docs/essentials/types/index.html b/docs/essentials/types/index.html index 0cc1822..3cd0101 100644 --- a/docs/essentials/types/index.html +++ b/docs/essentials/types/index.html @@ -3,19 +3,19 @@ -Wrapper Types | NeoHaskell +Meeting the Types | NeoHaskell - +
-

Wrapper Types

caution

The documentation that you're reading is a design document where most of +

Meeting the Types

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

One of the key features of NeoHaskell is its type system. Some people have preconceived ideas about types, and they think that they get in the way of development process too much. This is usually because either the type system of the programming language they @@ -76,7 +76,7 @@ or DDD, which is the way of designing software that NeoHaskell inspires from.

NeoHaskell's way of approaching software development is very different to other programming languages, because it makes you think about the design of your program before you even start writing it. At first, it might seem like a lot of work, but in exchange, you get tools that make your life easier, and your head free of worries.

- + \ No newline at end of file diff --git a/docs/extra-topics/faq/index.html b/docs/extra-topics/faq/index.html index d56112c..a11ae02 100644 --- a/docs/extra-topics/faq/index.html +++ b/docs/extra-topics/faq/index.html @@ -10,15 +10,15 @@ - +
-

FAQ

This page compiles questions that are commonly asked on Discord.

If your question is not listed here, feel free to reach out in the chat so +

FAQ

This page compiles questions that are commonly asked on Discord.

If your question is not listed here, feel free to reach out in the chat so the community can help you find the way.

Who's the target audience for NeoHaskell?

Software developers that are comfortable (not experts) with a statically typed mainstream language, who are interested in developing their own projects easily.

You can find more in-depth info in the NHEP 2.

- + \ No newline at end of file diff --git a/docs/extra-topics/integrating-neohaskell/index.html b/docs/extra-topics/integrating-neohaskell/index.html index c5e8c1d..41afb6c 100644 --- a/docs/extra-topics/integrating-neohaskell/index.html +++ b/docs/extra-topics/integrating-neohaskell/index.html @@ -10,14 +10,14 @@ - +
-

Integrating NeoHaskell

caution

The documentation that you're reading is a design document where most of +

Integrating NeoHaskell

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

TODO

caution

UNDER CONSTRUCTION

Work in progress

- + \ No newline at end of file diff --git a/docs/extra-topics/neohaskell-genesis/index.html b/docs/extra-topics/neohaskell-genesis/index.html index 118f000..36c0eaf 100644 --- a/docs/extra-topics/neohaskell-genesis/index.html +++ b/docs/extra-topics/neohaskell-genesis/index.html @@ -10,14 +10,14 @@ - +
-

The NeoHaskell Genesis

The Beginning: Complexity Generation

Once upon a time, there was a developer who embarked on a project written in Haskell—a language known for its expressive power and strong type system. Consumed by the allure of purely functional programming, this developer started off with enthusiasm and hope. Yet as time passed, the Haskell codebase, once a work of intellectual beauty, turned increasingly cumbersome and enigmatic, as the team +

The NeoHaskell Genesis

The Beginning: Complexity Generation

Once upon a time, there was a developer who embarked on a project written in Haskell—a language known for its expressive power and strong type system. Consumed by the allure of purely functional programming, this developer started off with enthusiasm and hope. Yet as time passed, the Haskell codebase, once a work of intellectual beauty, turned increasingly cumbersome and enigmatic, as the team kept adding on newer and fancier features.

The project was eventually pulled from production, but its lessons were far from lost. Over the subsequent five years, the developer turned to TypeScript, Python, and Java—languages celebrated for their wide utility and enterprise appeal. While the code-bases in these languages weren't inherently complex in their syntax, a new challenge emerged: cognitive overload, exacerbated by the side effects and unpredictability that came with these languages. Though the lines of code were straightforward, the resulting complexity was covertly overwhelming.

The Awakening: NeoHaskell Emerges

Confronted by these extremes, it became clear that a middle ground was sorely needed. And so, the concept of NeoHaskell was conceived, drawing wisdom from experiences in Domain-Driven Design, Event Sourcing, CQRS, and the craftsmanship intrinsic to software development.

The ambition? To rebuild Haskell from its foundations, creating a language designed to combat both code complexity and accidental complexity—those problems that don't arise from the domain but from the tools and methodologies employed.

The goal: Focus on what's important

Beginner-Friendliness

The first lesson from the initial Haskell venture was its steep learning curve. NeoHaskell, therefore, aims to be approachable. It adopts a syntax and paradigms that are less intimidating for newcomers while still preserving the essential capabilities that Haskell offers.

Productivity

In crafting NeoHaskell, an emphasis has been placed on developer productivity. It encourages the creation of rich, bounded contexts and aggregate roots, allowing the developer to focus more on solving actual domain problems rather than wrestling with technicalities.

Complexity Mitigation

Inspired by Command-Query Responsibility Segregation and Event Sourcing design patterns, NeoHaskell promotes a clean architecture where read and write operations are distinctly separated. By keeping an immutable record of state changes, it brings predictability into the system, thereby mitigating both cognitive and code complexity.

Joy of Use

NeoHaskell is not merely a tool; it is a craft. It aspires to make coding not just functional but also pleasurable. The language aims to trigger a flow state, making each moment of development a joyful experience.

The NeoHaskell Promise

As it continues to evolve, NeoHaskell aspires to be a nexus of balance: simple yet potent, utilitarian yet principled, designed to cut through complexity while offering a pleasurable user experience. It doesn't just aim to be another programming language; it hopes to be a landmark in the landscape of software development—a testament to the hard-won lessons from a history rife with complexities.

- + \ No newline at end of file diff --git a/docs/getting-started/dogma/index.html b/docs/getting-started/dogma/index.html index 5488bfb..569af4d 100644 --- a/docs/getting-started/dogma/index.html +++ b/docs/getting-started/dogma/index.html @@ -10,13 +10,13 @@ - +
-

The NeoHaskell Dogma

A Dogma is not bad if it is aligned with the principles of the individual, and flexible enough for the moments it isn't.

The NeoHaskell Statements

  • Beautiful is better than ugly
  • Simple is better than complex
  • Duplicate and easy is better than concise and complex
  • Mathematics are good when not seen
  • The 95% of type-safety should be done with 5-or-less% of the effort
  • Instead of investing effort in the other 5%, better release to prod
  • If it takes more than 15 minutes to understand, it's a bug
  • The functional way is better, unless the imperative one is easier

Curious about the above? Keep reading!

- +

The NeoHaskell Dogma

A Dogma is not bad if it is aligned with the principles of the individual, and flexible enough for the moments it isn't.

The NeoHaskell Statements

  • Beautiful is better than ugly
  • Simple is better than complex
  • Duplicate and easy is better than concise and complex
  • Mathematics are good when not seen
  • The 95% of type-safety should be done with 5-or-less% of the effort
  • Instead of investing effort in the other 5%, better release to prod
  • If it takes more than 15 minutes to understand, it's a bug
  • The functional way is better, unless the imperative one is easier

Curious about the above? Keep reading!

+ \ No newline at end of file diff --git a/docs/getting-started/installing-neohaskell/index.html b/docs/getting-started/installing-neohaskell/index.html index 1c2035d..481f0fa 100644 --- a/docs/getting-started/installing-neohaskell/index.html +++ b/docs/getting-started/installing-neohaskell/index.html @@ -10,12 +10,12 @@ - +
-

Installing NeoHaskell

caution

The documentation that you're reading is a design document where most of +

Installing NeoHaskell

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

All of NeoHaskell's tasks are handled by the Neo command line tool. You might think of it as the chosen one (The Matrix™️ pun, sorry).

Neo will install all the required stuff for you, so you don't have to worry about it.

Getting Neo

In order to install Neo, copy and paste the following command in your terminal (if you're on Windows, use PowerShell as Administrator):

curl --proto '=https' --tlsv1.2 -sSf https://sh.neohaskell.org | sh
note

For many commands, Neo uses git under the hood. If you don't have it, you can read a guide on how to install it here.

Trying the Installation

Once everything is installed, usually the common thing is to try that @@ -28,7 +28,7 @@ commands that have subcommands. If we run neo run help, we'll see something like this:

$ neo run help
Neo: I'm here to help you.

Usage: neo run <command> [options]

Commands:
app Run the application
repl Run the interactive console
test Run the tests
format Run the formatter

You can keep using the help command to see what each command does!

Next Steps

Now that you have Neo installed, we're going to start getting our hands dirty, with the Neo interactive console. Let's go to the next section!

- + \ No newline at end of file diff --git a/docs/getting-started/interactive-console/index.html b/docs/getting-started/interactive-console/index.html index 669eb93..671204c 100644 --- a/docs/getting-started/interactive-console/index.html +++ b/docs/getting-started/interactive-console/index.html @@ -10,12 +10,12 @@ - +
-

Playing with the REPL

caution

The documentation that you're reading is a design document where most of +

Playing with the REPL

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

Even though NeoHaskell is a compiled language, it provides an interpreted mode which is much faster for the development process. In any moment, you can run neo run:repl and it will start an interactive console where you @@ -25,7 +25,7 @@ It's the same as writing 7207^{20} in math.

Anyway, to quit the REPL, you can press Ctrl + C or write :quit.

Exploring the REPL commands

At any time, you can write :help to see the list of commands available.

Don't worry too much about them if they seem a bit overwhelming. You'll learn them as you go.

In the next sections, we will start trying NeoHaskell from the REPL.

- + \ No newline at end of file diff --git a/docs/getting-started/intro/index.html b/docs/getting-started/intro/index.html index e13f16b..42ec457 100644 --- a/docs/getting-started/intro/index.html +++ b/docs/getting-started/intro/index.html @@ -10,12 +10,12 @@ - +
-

Introduction

caution

The documentation that you're reading is a design document where most of +

Introduction

caution

The documentation that you're reading is a design document where most of the features you're reading are yet to be implemented. Check the Note on the Docs

What is NeoHaskell?

NeoHaskell is a language for building and maintaining applications with the minimal amount of headaches possible. It builds on top of industrial-grade tools like GHC to allow developers of any level to be productive in a wide range of fields. It compiles to native code, and runs natively on your machine, allowing it @@ -41,7 +41,7 @@ Therefore, everything is packed into the neo command line tool.

What is everything? Well, everything that you need for developing your application:

  • Installing NeoHaskell
  • Generating a new project
  • Generating commonly-shaped files
  • Generating documentation
  • Managing dependencies
  • Compiling your code
  • Linting
  • Formatting
  • Running your tests
  • Packing your application for deployment

And every use case that helps you to be more happy along the way.

Begin your Journey

This has been a high-level introduction into the language, now we will roll up our sleeves and get our hands dirty. Happy hacking!

- + \ No newline at end of file diff --git a/index.html b/index.html index 01e6051..441b04b 100644 --- a/index.html +++ b/index.html @@ -10,13 +10,13 @@ - +

A language that triggers
flow state.

NeoHaskell is a dialect of Haskell that is focused on newcomer-friendliness and productivity.

It is designed to be easy to learn and use, while also being powerful enough to release your app with minimum effort and maximum confidence.

Features

verify = do
joe <- getPerson "Joe"
if joe.age > 18 then
print "Joe is an adult"
else
print "Joe is a child"

A Static that feels like Dynamic

NeoHaskell's type system is so powerful that you'll forget it's there. With type inference, you'll spend less time annotating and more time coding in a language that feels dynamic.

let infiniteList = [1..]
let doubledList = List.map (*2) infiniteList
print (List.toString doubledLis)

Lazy Evaluation for Eager Minds

NeoHaskell won't evaluate an expression until it's absolutely needed. This lets you create more efficient code and focus on what to compute rather than how to compute it.

foo = do
threadDelay 500
print "foo"

bar = do
threadDelay 1000
print "bar"

-- will print "foo" and exit
main = race foo bar

Fearless Concurrency

Concurrency is not an afterthought in NeoHaskell; it's baked right in. Write concurrent code without the common pitfalls, and get more done, faster.


Functions like async and race make it easy to write concurrent code. And with concepts like Channels, you'll be able to write concurrent code without the headaches.

reduction = do
myEvents <- getEvents "myAggregate"
return (
Stream.reduce
myReducer
initialState
myEvents
)

Event Sourcing and CQRS Ready

NeoHaskell aligns well with modern architectural patterns like Event Sourcing and CQRS, making it easier for you to build scalable and maintainable systems.

Coming Soon

"NeoHaskell is cool"
|> Text.toWordList
|> List.map Text.length
|> List.map (\x -> x * x)
|> List.takeIf Int.isEven

A beautiful standard library

NeoHaskell offers a clean and intuitive language design, complemented by a core library that provides all the tools you need to start creating amazing software right away.

$ neo setup
Setting up NeoHaskell...
Downloading the latest version of the compiler...
Installing build process plugins...
Done!

$ neo build
Building...

$ neo add scotty
Adding 'scotty' to your project...
Installing dependencies...
Done!

An integrated build system

NeoHaskell's CLI tool installs all required utilities and makes multi-platform support a breeze. Whether you're compiling to native code, installing dependencies, or packaging your application for deployment, you'll enjoy a seamless experience.

screen = do
body [] do
image [source "/img.png"]
text [] "Hello World!"

Mobile development

Build mobile applications with NeoHaskell. The CLI tool lets you generate a client package with auto-update support, and the generation of UIs is a breeze with the included UI library.

main = eventSourcing
{ entities = myEntities
, events = myEvents
, reducer = myReducer
, ...
} |> runApp

Pre-thought architecture

Whether you're building a command-line script, a backend, or a UI application, NeoHaskell has the architecture pre-thought, for you. Just generate your project from one of our templates, or just follow the guides in the documentation to setup your project for success!

answer question = [python|
from langchain.llms import OpenAI
llm = OpenAI()
return llm.predict("hi!")
|]

Python interop

With the rise of the AI era, it is now more important than ever to be able to leverage the power of Python's AI ecosystem. NeoHaskell, with its Python interoperability, lets you do just that.


Use LangChain, Stable Diffusion, PyTorch, and other Python libraries right from your NeoHaskell code.

A Community for People Like You

NeoHaskell is a community of people who are passionate about delivering software in an efficient and enjoyable way. We believe that programming should be fun, and that the best way to learn is to build things.

Therefore, NeoHaskell is a work in progress project. We are aiming to build a language that is delightful to learn and use, strongly focusing on optimizing developers' happiness.

Whether you're a freshman or a senior, a hacker or a rocket scientist, you'll find a place in our community.

Together, we are building the best language for the next generation of developers. Join us!

- + \ No newline at end of file