{"id":24092620,"url":"https://github.com/alemorales/modulartypes.jl","last_synced_at":"2026-06-10T06:37:04.011Z","repository":{"id":90426006,"uuid":"120739805","full_name":"AleMorales/ModularTypes.jl","owner":"AleMorales","description":"Macros implementing multitraits and generation of forward methods for type composition","archived":false,"fork":false,"pushed_at":"2018-02-19T10:50:48.000Z","size":73,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-10T07:59:27.397Z","etag":null,"topics":["julia","traits","type-composition"],"latest_commit_sha":null,"homepage":null,"language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AleMorales.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-02-08T09:26:59.000Z","updated_at":"2019-08-09T12:18:22.000Z","dependencies_parsed_at":"2023-10-20T16:31:55.311Z","dependency_job_id":null,"html_url":"https://github.com/AleMorales/ModularTypes.jl","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AleMorales%2FModularTypes.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AleMorales%2FModularTypes.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AleMorales%2FModularTypes.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AleMorales%2FModularTypes.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AleMorales","download_url":"https://codeload.github.com/AleMorales/ModularTypes.jl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240993952,"owners_count":19890419,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["julia","traits","type-composition"],"created_at":"2025-01-10T07:59:36.531Z","updated_at":"2026-06-10T06:37:03.942Z","avatar_url":"https://github.com/AleMorales.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ModularTypes\n\n[![Travis](https://travis-ci.org/AleMorales/ModularTypes.jl.svg?branch=master)](https://travis-ci.org/AleMorales/ModularTypes.jl)\n[![AppVeyor](https://ci.appveyor.com/api/projects/status/y5v7b53nyb0hwucd?svg=true)](https://ci.appveyor.com/project/AleMorales/modulartypes-jl)\n[![Codecov](https://codecov.io/gh/AleMorales/ModularTypes.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/AleMorales/ModularTypes.jl)\n\n\nModularTypes allows creating Julia types by making use of  type composition and\na multitrait-dispatch system. This package those situations where, unlike in\ngeneric programming, an algorithm and its associated data should not be decoupled.\nA common situation where such deocupling is not adequate is when building models\nby reusing modules (e.g. in agent based modelling), hence the name of the package.\n\n## What is the problem?\n\nType composition may fit naturally the concept of modular building (i.e. the\nmodel *has a* module rather than *is a* module). Even in examples that are used\nto introduce inheritance, type composition may be more powerful. For example,\nrather than saying \"a teacher is a person\" and a \"student is a person\" one may\nsay that a person may \"have the ability to teach\" or \"have the ability to learn\".\nIn this case, modules would be defined for \"Teaching\" and \"Learning\" that provide\nall the methods and data required to implement such abilities. This avoids having\nto redesign the type hierarchy whenever a change is introduced in the system (i.e.\nreuse of data and functionality is horizontal rather than hierarchical).\n\n```julia\nstruct Teacher\n  teaching::Teaching\n  ...\nend\nstruct Student\n  learning::Learning\n  ...\nend\n```\n\nWhere `...` represents other abilities we may want to confer to `Teacher`s and\n`Student`s, such `Eating`, `Driving`, etc. A hierarchical relationship can still\nbe emulated by composing types at multiple levels (like a matryoshka doll).\n\nHowever, with type composition, the person would not really acquire the\nability to teach or learn, which remain attached to the fields to which the Teaching\nor Learning modules were attached. That is, instead of `teach(t::Teacher, s::Student)`\none would have to say `teach(t.teaching::Teaching, s.learning::Learning)`. This\ncode suffers from a *semantic displacement* as the original intention was to make\nthe teacher teach, and the fields teaching and learning do not represent an actual\nentity. This can get particularly complicated if types are nested at multiple levels.\nThe solution to this problem is to generate *forwarding methods* that corrects the\nsemantic displacement as in:\n\n```julia\nteach(t::Teacher, s::Student) = teach(t.teaching, s.learning)\n```\n\n## What does this package do?\n\nModularTypes provides macros that automatically generate these forwarding\nmethods for any existing Julia type and make them available when included\nin other types as described in the above. This is achieved by using a multitraits\nsystem, similar in nature to the package [SimpleTraits.jl](https://github.com/mauro3/SimpleTraits.jl).\n\n### Multitraits\n\nThe main difference with respect to SimpleTraits.jl is that traits are organized\ninto trait classes and that a trait is a property defined when generating the methods\n(i.e. any existing type can be used as a trait).\n\nA trait class is then use to dispatch the same function for different traits\nincluded in the class. Both traits and trait classes are single parameter but\nmultiple traits may be used within a function signature. Four colons (`::::`)\nare used to denote function arguments that are traits or trait classes. This is\ninspired by [Traitor.jl](https://github.com/andyferris/Traitor.jl).\n\nThe multitrait system may be used independent of type composition. The trait\ndispatch method and the specific trait methods are implement by the macros\n`@traitdispatch` and `@traitmethod`. For example:\n\n```julia\n# Type to be used for trait dispatching\nstruct TC end\n# Create a dispatch method associated to a trait class\n@traitdispatch function foo(x::::TC) end\n# Type to be used as trait\nstruct T end\n# Create a method for trait T\n@traitmethod foo(x::::T) = x.x\n```\nThe macro `@hastrait` is then used to indicate than a given type implements a\ngiven trait. It is necessary to specify both the trait class and the trait\nbeing implement, as in the following example:\n\n```julia\nstruct bar\n    x::Int64\nend\n# Declare that bar has the trait T from trait class TC\n@hastrait bar TC{T}\nfoo(bar(3))\n```\n\nNote that only one trait per trait class should be implemented. Traits can be\nadded to a type at any moment after its definitions, and trait methods will become\navailable even if their are created after a trait is assigned to a type. If the\ntraits to be implemented are known at the time of type definition, the `@implements`\nmacro comes in handy:\n\n```julia\n@implements TC{T} struct baz\n    y::Int64\nend\n```\n\nIf you are used to define your types with `@with_kw` from the [Parameters.jl](https://github.com/mauro3/Parameters.jl)\npackage, you can use `@implements_kw`, which will automatically call `@with_kw`\n\n### Modular types\n\nIn the case that we want to create forwarding methods to correct the semantic\ndisplacement of type composition, the procedure is exactly the same as for\nmultitraits (see previous section) with the differences:\n\n* The `@forwardtraitmethod` macro should be used instead of `@traitmethod`\n* The type must be assigned to a field with the name `field\u003ctypename\u003e` where `\u003ctypename\u003e` is the name of the type.\n\nFor example:\n\n```julia\n# Type to be used for trait dispatching\nstruct fTC end\n# Create a dispatch method associated to a trait class\n@traitdispatch function fooz(x::::fTC) end\n# Just a regular type, for which a forwarding method will be created\nstruct fT\n    x::Int64\nend\n# Create a method for fT\n@forwardtraitmethod fooz(x::::fT) = x.x\n# Type that includes fT in fieldfT\nstruct fbar\n    fieldfT::fT\nend\n@hastrait fbar fTC{fT}\nfooz(fbar(fT(3)))\n```\n\nSimilarly to multitraits, if the traits are known at the moment of type definition,\nthe keywords `@contains` and `@contains_kw` may be used. The latter is particularly\nhandy as type composition can result in complex object construction. For example:\n\n```julia\n@contains_kw fTC{fT} = fT(1) struct fbarkw\nend\nfooz(fbarkw())\n```\n\n### Compatibility across modules\n\nNote that traits may live in a different module to the module where the forward methods\nare defined and/or the module where the container types are defined. Normal module\nprefixing may be used if the symbols are no imported in all the macros described\nin the above. However, the name of the field to which a type with forwarding methods\nis assigned should strip out all the module prefixing.\n\n### Containing multiple types\n\nA type may implement multiple traits and contain multiple types. When using `@implements`, `@contains` or their\n`kw` equivalents,  all the traits should be listed as different arguments of the macro call.\n`@contains` and its `kw` equivalent will insert the instances of the type-traits\nin the order in which they are listed after all the fields already existing. Then\nit will add the traits to the type in the same order. That is,\n\n```julia\n@contains TC1{T1} TC{T2} struct bar2\n  y::Int64\nend\n```\n\nis equivalent to:\n\n```julia\nstruct bar2\n  y::Int64\n  fieldT1::T1\n  fieldT2::T2\nend\n@hastrait bar2 TC1{T1}\n@hastrait bar2 TC2{T2}\n```\n\nOnly one trait class can dispatch a given method on a given namespace. That is,\n```julia\n@traitdispatch function foo(x::::TC, y) end\n@traitdispatch function foo(x::::TC2, y) end\n```\nwill result in the second definition overwriting the first. However,\n```julia\n@traitmethod function foo(x::::T, y) end\n@traitmethod function foo(x::::T2, y) end\n```\nwill work.\n\n### Keyword and optional arguments\n\nMethods and function signatures used with `@traitdispatch`, `@traitmethod` and\n`@forwardtraitmethod` may contain optional and keyword arguments. However, these\narguments cannot be used for trait dispatch. Also, the default values assigned\nin the `@traitdispatch` method will override any default values assigned in the\n`@traitmethod` or `@forwardtraitmethod` methods.\n\n### Parametric types\n\nParametric types may be used as traits and trait dispatch will correctly propagate\nthe type parameters. When composing a type from parametric types, the name of\nthe field should not take into account the type parameters. But the type parameters\nstill need to be considered in the type definition. That is:\n\n```julia\n@contains TC{T{T1,S1}} struct bar{T1,S1} end\n```\nis equivalent to:\n\n```julia\nstruct bar{T1,S1}\n  fieldT::T{T1,S1}\nend\n@hastraits bar{T1,S1} TC{T{T1,S1}}\n```\n\nNote that the `T1`s and `S1`s must coincide within the type definition and\nwithin the `@hastrait` macro. Type parameters may also be used in methods\nmodified by `@traitdispatch`, `@traitmethod` or `@forwardtraitmethod` and they\nwill be respected in the generated methods.\n\n## How does this work?\n\nThis is an implementation of inspired on the packages SimpleTraits.jl and Traitor.jl.\n\n`@traitdispatch` will takes an empty function, extract its signature and generates\na method where each argument qualified with `::::` is converted into a type parameter\nin the method signature. The body of the generated method is a call to the same\nfunction but with an extra argument for each trait used for dispatch. These\narguments are calls to a constructor with the same name as the trait class and\ntaking the type parameter as input. The extra argument go first, as otherwise it\nwould not be possible to use optional and keyword arguments. That is:\n\n```julia\n@traitdispatch function fun(x::::TC1, y::Int64, z::::TC2) end\n```\n\nwill generate\n\n```julia\nfun(x::traitTC1, y::Int64, z::traitTC2) where {traitTC1, traitTC2} =\n    fun(TC1(traitTC1), TC2(traitTC2), x, y, z)\n```\n\n`@traitmethod` will add new method definition based on its argument. In the signature\nof the extra method, each argument qualified with `::::` is left unqualified. In\naddition, a value type argument is added for each trait, matching the extra arguments generated by `@traitdispatch`. The body of the method remains unchanged. For example:\n\n```julia\n@traitmethod fun(x::::T1, y::Int64, z::::T2) = x+y+z\n```\n\nwill generate 2 methods\n\n```julia\nfun(x::T1, y::Int64, z::T2) = x+y+z\nfun(::Type{T1}, ::Type{T2}, x, y::Int64, z) = x + y + z\n```\n\nThe first method allows to bypass trait dispatch when using objects of type `T1` and `T2`. The second method is the one that will be executed when `x` and `z` are objects\nthat have the traits `T1` and `T2` (i.e. that behave as if they were of type `T1` and\n`T2` as far as `fun` is concerned).\n\n`@hastrait` will define the constructors required by `@traitdispatch`, that\nreturns the type associated to a trait when taking as argument the\ntype implementing the trait. That is\n\n```julia\n@hastrait bar TC{T}\n```\n\nwill generate\n\n```julia\nTC(::Type{bar}) = T\n```\n\n`@forwardtraitmethod` will take a method definition and add an extra method.\nThe signature of the extra method is modified as in `@traitmethod`. However,\nthe body of the method is subtituted by a call to the original method, but\nsubstituying any argument that is qualified by a trait with a reference to\nthe field of the correct name (i.e. `field\u003ctrait_name\u003e`). For example:\n\n```julia\n@forwardtraitmethod fun(x::::T1, y::Int64, z::::T2) = x+y+z\n```\n\nwill generate\n\n```julia\nfun(x::T1, y::Int64, z::T2) = x+y+z\nfun(::Type{T1}, ::Type{T2}, x, y::Int64, z) = fun(x.fieldT1, y, z.fieldT2)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falemorales%2Fmodulartypes.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falemorales%2Fmodulartypes.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falemorales%2Fmodulartypes.jl/lists"}