{"id":23902660,"url":"https://github.com/mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop","last_synced_at":"2026-06-21T00:35:38.741Z","repository":{"id":226978151,"uuid":"763167778","full_name":"mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop","owner":"mtumilowicz","description":"Introduction to typelevel programming: phantom types, dependent types, path dependent types and Curry-Howard isomorphism.","archived":false,"fork":false,"pushed_at":"2025-01-26T09:34:05.000Z","size":332,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-23T10:44:17.899Z","etag":null,"topics":["compile-time-meta-programming","curry-howard-isomorphism","dependent-type-theory","dependent-types","first-order-logic","idris","path-dependent","path-dependent-types","phantom-types","polymorphic-functions","polymorphic-types","scala3","scala3-metaprogramming","type-programming","typed-lambda-calculus","typelevel-programming","union-types","workshop","workshop-materials"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mtumilowicz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-25T18:21:17.000Z","updated_at":"2025-02-11T20:45:01.000Z","dependencies_parsed_at":"2024-12-12T21:23:32.232Z","dependency_job_id":"bbdca798-ca65-4578-9cbf-4404c6176228","html_url":"https://github.com/mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop","commit_stats":null,"previous_names":["mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtumilowicz","download_url":"https://codeload.github.com/mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34590214,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compile-time-meta-programming","curry-howard-isomorphism","dependent-type-theory","dependent-types","first-order-logic","idris","path-dependent","path-dependent-types","phantom-types","polymorphic-functions","polymorphic-types","scala3","scala3-metaprogramming","type-programming","typed-lambda-calculus","typelevel-programming","union-types","workshop","workshop-materials"],"created_at":"2025-01-04T22:49:41.851Z","updated_at":"2026-06-21T00:35:38.701Z","avatar_url":"https://github.com/mtumilowicz.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# scala3-dependent-types-polymorphic-functions-phantom-types-workshop\n[![Build Status](https://app.travis-ci.com/mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop.svg?branch=main)](https://app.travis-ci.com/mtumilowicz/scala3-dependent-types-polymorphic-functions-workshop)\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n\n* references\n    * [\"Scala vs Idris: Dependent types, now and in the future\" by Miles Sabin and Edwin Brady (2013)](https://www.youtube.com/watch?v=fV2no1Rkzdw)\n    * [Type level Programming in Scala - Matt Bovel](https://www.youtube.com/watch?v=B7uficxARKM)\n    * [Why Netflix ❤'s Scala for Machine Learning - Jeremy Smith \u0026 Aish](https://www.youtube.com/watch?v=BfaBeT0pRe0)\n    * [Zymposium — Path Dependent Types](https://www.youtube.com/watch?v=w2rcHCqdn-o)\n    * [f(by) 2020: Dependent types, Vitaly Bragilevsky](https://www.youtube.com/watch?v=ohG-PRwOorA)\n    * [Stephan Boyer - What are Dependent Types - λC 2017](https://www.youtube.com/watch?v=FquVty-Ghpg)\n    * [Guillaume Martres - Polymorphic Function Types in Scala 3](https://www.youtube.com/watch?v=sauaDZ-1-zM)\n    * [Type Members vs Type Parameters - NE Scala 2016](https://www.youtube.com/watch?v=R8GksuRw3VI)\n    * https://github.com/milessabin/strangeloop-2013/tree/master\n    * https://github.com/mbovel/scalacon-typelevel-operations\n    * https://github.com/hablapps/syllogisms\n    * https://docs.scala-lang.org/sips/42.type.html\n    * https://mypy.readthedocs.io/en/stable/literal_types.html#\n    * https://chat.openai.com\n    * https://github.com/goldfirere/singletons\n    * https://stackoverflow.com/questions/46748559/i-need-help-haskell-inhabitant-for-the-type\n    * https://typesandkinds.wordpress.com/2013/12/17/singletons-v0-9-released/\n    * https://www.poberezkin.com/posts/2020-05-17-using-dependent-types-haskell-singletons.html\n    * https://dl.acm.org/doi/10.1145/2364506.2364522\n    * https://stackoverflow.com/questions/12961651/why-not-be-dependently-typed\n    * https://www.doubloin.com/learn/formal-verification-smart-contracts\n    * https://ethereum.org/en/developers/docs/smart-contracts/formal-verification/\n    * https://www.certik.com/resources/blog/3UDUMVAMia8ZibM7EmPf9f-what-is-formal-verification\n    * https://lampwww.epfl.ch/~amin/dot/fpdt.pdf\n    * https://stackoverflow.com/questions/24960722/what-is-the-difference-between-path-dependent-types-and-dependent-types\n    * https://users.rust-lang.org/t/concatenating-arrays/89538/2\n    * https://stackoverflow.com/questions/12935731/any-reason-why-scala-does-not-explicitly-support-dependent-types/12937819#12937819\n    * https://www.degruyter.com/document/doi/10.1515/9783110657883-018/html?lang=de\n    * https://kiranvodrahalli.github.io/notes/curry_howard_cos510notes.pdf\n    * https://studenttheses.uu.nl/bitstream/handle/20.500.12932/36496/Thesis%20Jasmijn%20van%20Harskamp.pdf\n    * http://www.ivanociardelli.altervista.org/wp-content/uploads/2017/01/Sorensen-excerpt.pdf\n    * https://softwarefoundations.cis.upenn.edu/lf-current/ProofObjects.html\n    * https://www.quora.com/What-is-an-intuitive-explanation-of-the-Curry-Howard-correspondence\n    * https://cstheory.stackexchange.com/questions/50714/why-is-the-curry-howard-isomorphism\n    * https://docs.scala-lang.org/scala3/reference/new-types/polymorphic-function-types.html\n    * https://docs.scala-lang.org/scala3/reference/new-types/type-lambdas.html\n    * https://www.baeldung.com/scala/type-lambdas-scala-3\n    * https://github.com/typelevel/kind-projector\n    * https://stackoverflow.com/questions/39905267/what-is-a-kind-projector\n    * https://medium.com/scala-3/scala-3-type-lambdas-polymorphic-function-types-and-dependent-function-types-2a6eabef896d\n    * https://blog.rockthejvm.com/scala-3-type-lambdas/\n    * https://stackoverflow.com/questions/51131067/when-are-dependent-types-needed-in-shapeless\n    * https://chat.openai.com/\n    * https://gemini.google.com/\n    * https://medium.com/@Webmarmun/dependent-types-in-haskell-f35b8880cc16\n    * https://medium.com/background-thread/the-future-of-programming-is-dependent-types-programming-word-of-the-day-fcd5f2634878\n    * https://ps.informatik.uni-tuebingen.de/teaching/ws15/pdt/\n    * https://xebia.com/blog/dependent-and-refinement-types-why/\n    * https://www.reddit.com/r/ProgrammingLanguages/comments/10f1fr0/basic_building_blocks_of_dependent_type_theory/\n    * https://yarax.medium.com/from-logic-and-math-to-code-for-dummies-part-i-242183267efd\n    * https://en.wikipedia.org/wiki/Liar_paradox\n    * https://yarax.medium.com/from-logic-and-math-to-code-for-dummies-part-ii-higher-order-logic-5db1aa93eb35\n    * https://www.stackbuilders.com/blog/reverse-reverse-theorem-proving-with-idris/\n    * https://docs.scala-lang.org/scala3/reference/contextual/using-clauses.html\n    * https://dotty.epfl.ch/api/scala/\n    * [Scala Type-Level Operations – Matt Bovel](https://www.youtube.com/watch?v=6OaW-_aFStA)\n    * [Rodolfo Hansen - Keep Your Types Small](https://www.youtube.com/watch?v=2Orv_l8_EVQ)\n    * [Philip Wadler – Propositions as Types](https://www.youtube.com/watch?v=ru20eaMYbDo)\n    * [Emily Pillmore: Type Arithmetic and the Yoneda Lemma](https://www.youtube.com/watch?v=aXS5HZ_1fNQ)\n    * [Formal Logic Undressed — Paul Snively](https://www.youtube.com/watch?v=saMtzIaDCJM)\n    * https://milessabin.com/blog/2011/06/09/scala-union-types-curry-howard/\n    * https://medium.com/@maximilianofelice/builder-pattern-in-scala-with-phantom-types-3e29a167e863\n    * https://infoscience.epfl.ch/record/273667?ln=en\n    * https://xebia.com/blog/compile-safe-builder-pattern-using-phantom-types-in-scala/\n    * https://www.codecentric.de/wissens-hub/blog/phantom-types-scala\n    * https://www.scalamatters.io/post/phantom-types-without-phantom-pain\n    * [A Crash Course in Category Theory - Bartosz Milewski](https://www.youtube.com/watch?v=JH_Ou17_zyU)\n    * https://math.stackexchange.com/questions/2561353/set-of-functions-from-empty-set-to-0-1\n    * [Idris for (im)practical Scala programmers - Marcin Rzeźnicki - Chamberconf 2018](https://www.youtube.com/watch?v=zCJWv9X8eKM)\n    * https://leanpub.com/thinking-with-types/\n    * https://chatgpt.com\n\n## preface\n* goals of this workshop\n    * understanding dependent types\n        * comprehension how to apply that in practice based on scala3\n            * singleton types\n            * `=:=`\n            * type level programming\n            * polymorphic functions\n    * applying phantom types to provide compile-time proofs     \n    * understanding path dependent types\n        * applying knowledge in practice\n    * noticing correspondence between logic and computations\n        * formal proofs for basic tautologies\n        * implemetation of union types using `with`\n* workshop plan\n    1. `pt1_SizedList`\n        * context: rust\n            * in rust arrays has compile-time size\n                * reason: everything allocated on stack must have known size\n                    * array is allocated on stack\n                * example: https://play.rust-lang.org/\n                    ```\n                    fn main() {\n                        let array1: [i32; 3] = [1, 2, 3];\n                        let array2: [i32; 2] = [4, 5];\n                    }\n                    ```\n            * what rust can't do (at least - for now) is concatenating of two arrays of different size\n                * reason: no way to perform operations on types, for example: adding them\n                * solution: `generic_const`\n                    ```\n                    #![allow(incomplete_features)]\n                    #![feature(generic_const_exprs)]\n\n                    fn concat_arrays\u003cT, const A: usize, const B: usize\u003e(\n                        a: [T; A], b: [T; B]\n                    ) -\u003e [T; A+B]\n                    where\n                        T: Default,\n                    {\n                        let mut ary: [T; A+B] = std::array::from_fn(|_| Default::default());\n                        for (idx, val) in a.into_iter().chain(b.into_iter()).enumerate() {\n                            ary[idx] = val;\n                        }\n                        ary\n                    }\n\n                    fn main() {\n                        let array1: [i32; 3] = [1, 2, 3];\n                        let array2: [i32; 2] = [4, 5];\n\n                        let result_array: [i32; 5] = concat_arrays(array1, array2);\n\n                        println!(\"{:?}\", result_array);\n                    }\n                    ```\n        * task: implement collection that tracks its size at compile time\n            * use case: allow us to create matrices of a known size and check at compile time that they are multipliable\n            * implement safe version of `head` (fails compilation if invoked on empty list)\n            * example\n                * rust\n                    * example\n                        ```\n                        fn main() {\n                            let array1: [i32; 0] = [];\n\n                            let head = array1[0]; // does not compile: index out of bounds: the length is 0 but the index is 0\n                        }\n                        ```\n                * idris - https://tio.run/#idris\n                    ```\n                    data Vect : Nat -\u003e Type -\u003e Type where\n                      Nil : Vect Z a\n                      (::) : a -\u003e Vect n a -\u003e Vect (plus 1 n) a -- (plus 1 n) same as (S n)\n\n                    concat : Vect n a -\u003e Vect m a -\u003e Vect (n + m) a\n                    concat Nil ys = ys\n                    concat (x :: xs) ys = x :: concat xs ys\n\n                    head : Vect (plus 1 n) a -\u003e a\n                    head (x :: xs) = x\n\n                    v0 : Vect 0 a\n                    v0 = Nil\n                    v3 : Vect 3 Integer\n                    v3 = 10 :: 5 :: 1 :: Nil\n                    v4 : Vect 4 Integer\n                    v4 = 1 :: 2 :: 3 :: 4 :: Nil\n\n                    v3v4 : Vect 7 Integer\n                    v3v4 = concat v3 v4\n\n                    v3Head : Integer\n                    v3Head = head v3\n\n                    -- v0Head: Integer\n                    -- v0Head = head v0 -- not compiling, there is no function head for 0-sized vector\n\n                    main : IO ()\n                    main = putStrLn $ \"head of v1: \" ++ show v3Head\n                    ```\n    1. `pt2_SList`\n        * implement methods: `append` and `reverse` using `foldRightF`\n            * why normal `foldRight` is not enough?\n                * notice that in classical `foldRight` type of accumulator (`B`) cannot change during processing\n                    ```\n                    trait SList[N \u003c: Int]: // assume that SList has Strings\n                        def foldRight[B](z: B)(op: (String, B) =\u003e B): B\n\n                    // SList.foldRight(SNil) { case (elem, acc) =\u003e SCons(elem, acc) } // not compiles, SNil is SList[0] and SCons is SList[M]\n                    ```\n    1. `pt3_TypeSafeMethod`\n        * implement type safe version of `format` that validates arguments based on specified types\n            * should support\n                * `%s` -\u003e `String`\n                * `%d` -\u003e `Int`\n                * any arbitrary combination of them with every cardinality \u003e 1\n            * example\n                ```\n                tsFormat(\"%s is %d\")(\"s1\", 1) // compiles\n                tsFormat(\"%s %s %s is %d %s\")(\"s1\", \"s2\", \"s3\", 1, \"s4\") // compiles\n                tsFormat(\"%s is %d\")(i, s) // does not compile: Found: (i : Int) Required: String\n                ```\n        * explain why cardinality == 1 is complicating a bit implementation\n    1. `pt4_pathDependent`\n        * rewrite path dependent approach into generics\n        * discuss variance\n    1. `pt5_LogicalProofs`\n        * proof theorems:\n            1. (all S are M) and (all M are P) =\u003e all S are P\n            1. (not all S are M) and (all M are P) =\u003e not all S are P\n    1. `pt6_CurryHoward`\n        * using De Morgan's law implement equivalent of sum type: `|`\n        * use phantom types to apply that in method's as evidence\n\n## prerequisite\n* `summon[T]`\n    * find a given instance of type `T` in the current scope\n* `=:=`\n    * instance of `A =:= B` witnesses that the types `A` and `B` are equal\n    * example: proof of being the same type\n        ```\n        val i = 5 // val i: 5 = 5\n        summon[i.type =:= 5]\n        ```\n* `\u003c:\u003c`\n    * instance of `A \u003c:\u003c B` witnesses that `A` is a subtype of `B`\n\n## phantom types\n* called this way, because they never get instantiated\n    * used to represent type relationships rather that working directly with their values\n* commonly used to express constraints encoded in types\n    * to prove static properties of the code using type evidences\n* prevent some code from being compiled in certain situations\n    * useful when representing models that have a particularily defined structural state with transitions\n    * example: assume we need a function that turns a machine on (`turnOn`), only if it is turned off\n        ```\n        sealed trait MachineState\n        object MachineState {\n            sealed trait TurnedOn extends MachineState\n            sealed trait TurnedOff extends MachineState\n        }\n\n        case class Machine[State \u003c: MachineState](){\n          def open(implicit ev: State =:= Closed) = Door[Open]()\n          def close(implicit ev: State =:= Open) = Door[Closed]()\n        }\n        ```\n* are needed only for compilation\n    * do not come with extra runtime overhead\n* builder pattern context\n    * example: sql query builder\n        * problem: aggregation query without `group_by` will fail\n        * solution: ZIO SQL\n            * https://github.com/zio/zio-sql/blob/b63708a35fb27eeab7a7edf4e320809fce77b5fc/core/jvm/src/main/scala/zio/sql/select/Read.scala#L183\n    * case study: case class builder\n        * problem: verification that all fields are filled\n            ```\n            case class Person(firstName: String, lastName: String, email: String)\n\n            Person person = new PersonBuilder()\n                .firstName(\"Hello\")\n                .lastName(\"World\")\n                .build(); // email not set, handle situation\n            ```\n        * solution\n            * naive approach\n                1. push checks to runtime - throw runtime exception\n                    * loosing referential transparency\n                1. `build()` returns `Either[Error, Person]`\n                    * troublesome for caller\n            * phantom types approach\n                ```\n                class PersonBuilder[State \u003c: PersonBuilderState] private (\n                    val firstName: String,\n                    val lastName: String,\n                    val email: String) {\n                  def firstName(firstName: String): PersonBuilder[State with FirstName] =\n                    new PersonBuilder(firstName, lastName, email)\n                  def lastName(lastName: String): PersonBuilder[State with LastName] =\n                    new PersonBuilder(firstName, lastName, email)\n                  def email(email: String): PersonBuilder[State with Email] =\n                    new PersonBuilder(firstName, lastName, email)\n                  def build()(using State =:= FullPerson): Person = // phantom type\n                    Person(firstName, lastName, email)\n                }\n\n                object PersonBuilder {\n                  sealed trait PersonBuilderState\n                  object PersonBuilderState {\n                    sealed trait Empty extends PersonBuilderState\n                    sealed trait FirstName extends PersonBuilderState\n                    sealed trait LastName extends PersonBuilderState\n                    sealed trait Email extends PersonBuilderState\n                    type FullPerson = Empty with FirstName with LastName with Email\n                  }\n                  def apply(): PersonBuilder[Empty] = new PersonBuilder(\"\", \"\", \"\")\n                }\n                ```\n* ZIO environment parameter context is phantom type\n    * is internally used by ZIO to verify that we have provided all the required environment\n        * only programs `ZIO[Any, _, _]` are executable\n    * usually there is no type `R` that user can provide\n        * example: `ZIO[R1 with R2, E, A]`\n            * we need to either provide\n                1. `ULayer[R1]`, `ULayer[R2]`\n                1. `ULayer[R1 with R2]`\n                    * there is no value `R1 with R2`\n\n\n## singleton types\n* \"inhabitant of a type\" means an expression which has some given type\n    * example\n        ```\n        val length: String =\u003e Int = (s: String) =\u003e s.length\n        ```\n* usually types has more than one inhabitant\n    * `Boolean`: two values\n    * `Int`: `[Int.MinValue; Int: MaxValue]`\n    * `String`: infinitely many\n* notice that there are types that has no values\n    * type without any value is the \"bottom\" type\n    * example: `Function1[String, Nothing]`\n* singleton types = types which have a unique inhabitant\n    * examples\n        * `Unit` = only one inhabitant\n        * `(a: A, b: B)` = |A| x |B| number of inhabitants\n        * `Either(a: A, b: B)` = |A| + |B| number of inhabitants\n        * literal types = type inhabited by a single constant value known at compile-time (literal)\n            ```\n            val i5: 5 = 5\n            ```\n        * types inhabited by a single value not known at compile-time\n            ```\n            val userInput = StdIn.readInt()\n            val userInput2 = StdIn.readInt()\n            val iInput: userInput.type = userInput\n            val iInput2: userInput2.type = userInput // not compiling, compiles only with `= userInput2`\n            ```\n    * bridge the gap between types and values\n        * example: compile-time operations on types\n            ```\n            import scala.compiletime.ops.string.*\n            // type Length[X \u003c: String] \u003c: Int\n\n            val a: Length[\"Hello\"] = 5\n            val b: Length[\"Hello\"] = \"Hello\".length() // not compiling, length() returns general Int, but we need 5\n            ```\n            or for int\n            ```\n            import scala.compiletime.ops.int.*\n            // type +[X \u003c: Int, Y \u003c: Int] \u003c: Int\n\n            val result: 5 + 6 = 11\n            ```\n            note that operations could be added, suppose that we want to add `Read` type for strings\n            ```\n            import scala.compiletime.ops.string.Read // type we want to add\n\n            val a: Read[\"path/to/some/file\"] = \"Hello\\n\"\n\n            // 1. go to Dotty core -\u003e Types -\u003e AppliedType -\u003e tryNormalize -\u003e tryCompiletimeConstantFold\n            // 1. find where types are handled and add additional entry\n            case tpne.Read =\u003e constantFold1(stringValue, scala.io.Source.fromFile(_).mkString)\n            // 1. add Read name to `tpne` (StdNames)\n            final val Read: N = \"Read\"\n            // 1. add to compiletimePackageStringTypes\n            ```\n* Scala 3\n    * `Singleton` is used by the compiler as a supertype for singleton types\n        * example\n            ```\n            summon[42 \u003c:\u003c Singleton]\n            ```\n    * type inference widens singleton types to the underlying non-singleton type\n        * example\n            ```\n            summon[(42 \u0026 Singleton) \u003c:\u003c Int]\n            ```\n    * when a type parameter has an explicit upper bound of `Singleton`, the compiler infers a singleton type\n        * example\n            ```\n            def singletonCheck5[T \u003c: Singleton](x: T)(using ev: T =:= 5): T = x\n            val x = singleCheck42(5) // compiles\n\n            def typeCheck5[T](x: T)(using ev: T =:= 5): T = x\n            val x = typeCheck5(5) // not compiles: cannot prove that Int =:= (5 : Int)\n            ```\n* are a technique for \"faking\" dependent types in non-dependent languages\n    * good approximation of dependent types\n* bridges the gap in phase separation between runtime values and compile-time types\n    * example\n        ```\n        val stdInputLine: String = scala.io.StdIn.readLine()\n        val inputLine: stdInputLine.type = stdInputLine\n        ```\n* allow programmers to use dependently typed techniques to enforce rich constraints among the types\n    * example: using singletons provably`*` correct sorting algorithm\n        * more accurately, it is a proof of partial correctness\n        * `*` means: sorting algorithm compiles in finite time and when it runs in finite time =\u003e result is indeed a sorted list\n\n## polymorphic lambda\n* is a function type which accepts type parameters\n    * example\n        ```\n        def reverse[A](xs: List[A]): List[A] = xs.reverse // polymorphic method\n        val reverse2: [A] =\u003e List[A] =\u003e List[A] = [A] =\u003e (xs: List[A]) =\u003e reverse[A](xs) // polymorphic lambda\n        ```\n* are not to be confused with type lambdas\n    * polymorphic lambda describes type of a polymorphic value\n        * are applied in terms\n            * terms = type inhabitants (~ exist at runtime)\n                * example\n                    * `Nothing` has 0 terms\n                    * `Unit` has 1 term\n                    * `Boolean` has 2 terms\n    * type lambda is an actual function value at the type level\n        * are applied in types\n* type lambda\n    * lets one express a higher-kinded type directly, without a type definition\n        * types belong to kinds\n            * think of kinds as types of types\n            * example\n                * `Int` belongs to 0-level kinds\n                * `List[_]` belongs to 1-level kinds\n                    * it takes 0-level kind as type argument\n                    * similar to a function: takes a level-0 type and returns a level-0 type\n                        ```\n                        [X] -\u003e List[X]\n                        ```\n    * example\n        * type definition\n            * unparameterized with a type lambda: `type T = [X] =\u003e\u003e R`\n            * parameterized: `type T[X] = R`\n                * shorthand for an unparameterized definition\n            * unparameterized with a type lambda: `type T = [X] =\u003e\u003e R`\n    * defines a function from types to types\n        * type analog of “value lambdas”\n    * body of a type lambda can again be a type lambda\n        * curried type parameters\n    * before Scala 3, API designers had to resort to compiler plugins, namely kind-projector, to achieve the same level of expressiveness\n        * scala2: doesn’t allow us to use underscore syntax to simply say `Either[Throwable, _]`\n            ```\n            // type projection implementing the same type anonymously (without a name)\n            ({type L[A] = Either[Throwable, A]})#L\n            ```\n        * kind-projector: `Either[Throwable, *]`\n        * scala3: `[K] =\u003e\u003e Either[Throwable, K]`\n            * use case: `given Monad[[R] =\u003e\u003e Either[Throwable, R]]`\n\n## dependent types\n* gradation\n    * values depending on values: functions\n        * typed lambda calculi: term - term\n    * values depending on types: polymorphism\n        ```\n        def twice[A](a: A)(f: A =\u003e A): A = f(f(a))\n        ```\n    * types depending on values: dependent types\n    * types depending on types: type functions\n        * higher order types\n* dependent type systems: \"values may also appear in types\"\n* question of how to enforce invariants has two answers in dependent types\n    * intrinsic\n        * implies that a “wrong” value cannot be constructed at all\n        * example\n            ```\n            -- Agda intrinsic\n            get :: (xs : List a l) -\u003e Fin l -\u003e a // Fin is a type defined in such a way that it can only take values from 0 up to n - 1\n            ```\n    * extrinsic\n        * allow any input, but then require an additional proof of the fact that input is within constraints\n        * more similar to a refinement\n        * example\n            ```\n            -- Agda extrinsic\n            get :: (xs : List a l) -\u003e (n : Nat) -\u003e (inBounds n l) -\u003e a\n            ```\n* let you move some checks to the type system itself\n    * making it impossible to fail while the program is running\n* use cases\n    1. multiplying matrices\n        * encode matrix size in type and verify if multiply is possible at compile time\n    1. database queries\n        * type of valid queries depends on the \"shape\" of the database\n        * type of the result of a query depends on the query itself\n    1. communication protocols\n        * what answer is valid for what message\n    1. binary serialization\n        * all binary formats are described by dependent types\n            * exact meaning and layout of later bytes depend on some earlier bytes\n            * example: uncompressed picture\n                * starts with the size of the picture, number of color channels, bit depth, alignment;\n                followed by the raw data, whose size and interpretation depends on those parameters\n* Scala context\n    * type structure cannot be deduced from runtime structure\n        * example: filter in sized vector\n            ```\n            def filter(p: A =\u003e Boolean): SizedList[???, A] // size depends on runtime application of predicate\n            ```\n\n## path dependent types\n* Scala unifies concepts from object and module systems\n    * essential ingredient of this unification is to support objects that contain type members in addition to\n    fields and methods\n    * to make any use of type members, programmers need a way to refer to them\n        * some level of dependent types is required\n        * usual notion is that of path-dependent types\n* path dependent type is a specific kind of dependent type in which the type depends on a path\n* types which are distinguished by the values which are their prefixes\n* `Aux` pattern\n    * example\n        ```\n        trait Wrapper {\n          type A\n\n          def value: A\n        }\n\n        object Wrapper {\n\n          type Aux[A0] = Wrapper { type A = A0 }\n          def apply[A0](a: A0): Wrapper.Aux[A0] =\n            new Wrapper {\n              type A = A0\n              def value: A0 = a\n            }\n        }\n\n        val w: Wrapper = Wrapper(1)\n        val wAux = Wrapper(1) // Wrapper.Aux[Int]\n        val z = wAux.value + wAux.value // ok\n        val z = w.value + w.value // compilation fails, type of value is really hidden\n        ```\n* use cases\n    1. hiding internal state: `ZIO Schedule[-Env, -In, +Out]`\n        ```\n        trait Schedule[-Env, -In, +Out] extends Serializable { self =\u003e\n          import Schedule.Decision._\n          import Schedule._\n\n          type State\n\n          def initial: State\n        ```\n        * reason: putting state will make it complex\n            * state can be really long like window recur every 5 seconds etc\n            * we don't need to know what it is to work with schedule\n                * exposing it means exposing implementation detail\n        * why not use trait\n            * we want to keep it the same in every referred place\n        * source: https://github.com/zio/zio/blob/series/2.x/core/shared/src/main/scala/zio/Schedule.scala\n    1. type inference \u0026 partial application\n        * problem: for generics you can only specify all of them or not specify any of the\n            ```\n            trait Joiner[Elem, R] {\n                def join(xs: Seq[Elem]): R\n            }\n\n            def doJoin[T, R](xs: T*)(using j: Joiner[T, R]): R = j.join(xs)\n\n            given Joiner[CharSequence, String] with {\n              override def join(xs: Seq[CharSequence]): String = xs.mkString\n            }\n\n            given Joiner[String, String] with {\n              override def join(xs: Seq[String]): String = xs.mkString(\",\")\n            }\n\n            // for Joiner[Elem, R] you can only specify all of them or not specify any of the\n            doJoin[CharSequence, String](\"a\", \"b\", \"c\")\n            doJoin[String, String](\"a\", \"b\", \"c\")\n            doJoin(\"a\", \"b\", \"c\")\n            ```\n        * use case: some subset of types is uniquely determined by other types\n            * example: ZIO Zippable\n                * source: https://github.com/zio/zio/blob/series/2.x/core/shared/src/main/scala/zio/Zippable.scala\n                * no matter how we zip we should always maintain \"flat\" structure of tuple\n                    * `((_, _), _)` ~ `(_, (_, _))` ~ `(_, _, _)`\n                    * example\n                        ```\n                        val zio1: ZIO[Any, Nothing, Int] = ZIO.succeed(1)\n                        val zio2: ZIO[Any, Nothing, Int] = ZIO.succeed(2)\n                        val zio3: ZIO[Any, Nothing, Int] = ZIO.succeed(3)\n\n                        val zio1_4: ZIO[Any, Nothing, ((Int, Int), Int)] = zio1 \u003c*\u003e zio2 \u003c*\u003e zio3 // ZIO 1: not flattened tuple\n                        val zio2_4: ZIO[Any, Nothing, (Int, Int, Int)] = zio1 \u003c*\u003e zio2 \u003c*\u003e zio3 // ZIO 2: no tuples nesting\n                        ```\n                * digression: it cannot be resolved systematically\n                    ```\n                    val zio1 = ZIO.succeed(1)\n                    val zio2 = ZIO.succeed((2, 3))\n                    val zio3 = ZIO.succeed(3)\n\n                    val zio2_4: ZIO[Any, Nothing, (Int, (Int, Int), Int)] = zio1 \u003c*\u003e zio2 \u003c*\u003e zio3 // no implicit for that case\n                    ```\n\n## Curry-Howard isomorphism\n* both logic and programming with functions are built around the notion of hypotheticals\n    * proposition `𝐴→𝐵` says \"if I had an 𝐴, I could prove 𝐵\"\n    * function of type `𝐴→𝐵` says \"if I had a value of type 𝐴, I could compute a value of type 𝐵\"\n    * these logics/languages are really systems for hypothetical reasoning, which we need for both programming and proving\n    * whether we say \"prove\" or \"compute\" really just depends on whether we only care about\n        * existence of an 𝐵\n        * or which 𝐵 we get\n* propositions as types\n    * bottom type = logical falsehood\n        * Scala’s `Nothing` type\n        * used to define type negation\n            ```\n            type Not[A] = A =\u003e Nothing\n            ```\n            * on the logical side of Curry-Howard this maps to `A -\u003e false`, which is equivalent to `~A`\n        * polymorphic function `absurd[A]: Nothing =\u003e A` corresponds to statement \"from falsehood you can derive everything\"\n            * it cannot be constructed, but intuitively it is just a promise: if you give me element of nothing I will\n            give you element of `A`\n                * usually called `absurd[A]: Nothing =\u003e A`\n                * notice that for any set 𝐴, there is exactly one function from the empty set to 𝐴\n                    * graph of an empty function is a subset of the Cartesian product ∅×𝐴\n                        * since the product is empty the only such subset is the empty set ∅\n    * function type = implication\n        * example: proof that `(a^b)^c = a^(b x c)`\n            * logic: `(c⟹(b⟹a))⟺((b∧c)⟹a)`\n            * types: `((b, c) -\u003e a) \u003c=\u003e (c -\u003e b -\u003e a)`\n                ```\n                curry :: ((b, c) -\u003e a) -\u003e c -\u003e b -\u003e a\n                curry g c b = g (b, c)\n\n                uncurry :: (c -\u003e b -\u003e a) -\u003e (b, c) -\u003e a\n                uncurry f (b, c) = f c b\n                ```\n    * product type = conjunction\n    * sum type = disjunction\n    * inhabited types = provable theorems\n        * we cannot implement generic function `f: A =\u003e B`\n            * it would mean we can prove implication `A -\u003e B` - from any information `A` we can derive any information `B`\n            * to do that, we need some kind of connection between `A` and `B`\n    * summary\n        | Algebra | Logic       | Types           |\n        |---------|-------------|-----------------|\n        | a + b   | a ∨ b       | Either a b      |\n        | a × b   | a ∧ b       | (a, b)          |\n        | b^a     | a ⇒ b       | a -\u003e b          |\n        | a = b   | a ⇔ b       | isomorphism     |\n        | 0       | ⊥           | Void            |\n        | 1       | ⊤           | ()              |\n* relates systems of formal logic to models of computation\n    * propositions as types\n        * useful way to think of types is to view them as predictions\n            * if the expression terminates, you know what form the expression is\n    * proofs as programs\n        * proof of a proposition is a program of that type\n        * provability corresponds to inhabitation\n            * if we can find the values that exist for a given a type, it turns out that the type corresponds to a true mathematical theorem\n    * normalisation of proofs as evaluation of programs\n* propositional calculus\n    * implication, negation, conjunction, disjunction, exclusive OR and equality\n    * problem: doesn’t know about sets, considering just atomic values\n* first-order logic\n    * extends propositional logic\n        * introduces quantifiers to atomic values\n            * Universal quantification ∀\n            * Existential quantification ∃\n    * corresponds to dependent types\n        * statement: for all x, if x is a student then x has an ID\n            ```\n            trait Student { type Id }\n            ```\n    * is undecidable\n        * Gödel’s incompleteness theorem, which says that even in the formal complete system you can come across with unprovable statements\n            * example: \"this statement is not provable\"\n                * case 1: this statement is false =\u003e it is provable =\u003e we proved something that is false\n                    * goes agains whole idea of proofs\n                    * if you can proove things that are false =\u003e logic is not very useful\n                * case 2: this statement is true =\u003e we have statements that are not provable\n* second-order logic\n    * apply quantifiers not only to atomic values but to sets and predicates as well\n        * example: there exists a property that holds for all natural numbers greater than 5\n            * vs FOL: we can say at most that for property P(x) we have: for all natural numbers greater than 5 P(x) holds\n    * corresponds to polymorphic types\n        * statement: ∃ P : Students → Bool, ∀ s : Students, hasPassed(s) = P(s)\n            ```\n            trait StudentPredicate[-A] {\n              def test(student: A): Boolean\n            }\n\n            def hasPassed[A](student: A)(using predicate: StudentPredicate[A]): Boolean =\n              predicate.test(student)\n            ```\n* has practical implications in e.g. program verification\n    * example: proof that `reverse o reverse == identity`\n        * by induction and with lemma `reverse (xs ++ ys) == reverse ys ++ reverse xs`\n    * formal verification is an automated process that uses mathematical techniques to prove the correctness of the program\n        * can prove that program's business logic meets a predefined specification\n    * formal model is a mathematical description of a computational process\n        * provide a level of abstraction over which analysis of a program's behavior can be evaluated\n* in some sense, the Curry-Howard isomorphism isn't an isomorphism at all\n    * some people prefer the word \"correspondence\"\n    * maybe it's not \"two things that are isomorphic\" but \"two different views of the same thing\"\n        * example: `a^1 = a`\n            * when viewed through Curry–Howard, it describes an isomorphism between `() -\u003e a` and `a`\n            * no distinction between having a value and having a (pure) program that computes that value","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala3-dependent-types-polymorphic-functions-workshop/lists"}